騰訊、和諧汽車、富士康“互聯網+智慧電動汽車”專案公佈企業負責人

和諧富騰互聯網加智慧電動汽車公司(以下簡稱“和諧富騰”)日前宣佈旗下“互聯網+智慧電動汽車”企業負責人,畢福康博士(Dr. Carsten Breitfeld)將擔任該企業首席執行官,戴雷博士(Dr. Daniel Kirchert)將擔任首席運營官。兩位管理層到任後將即刻啟動企業的運營。畢福康博士和戴雷博士亦將是該企業的事業合夥人和公司董事會成員。

左:畢福康博士(Dr. Carsten Breitfeld);右:戴雷博士(Dr. Daniel Kirchert)

和諧富騰是由中國和諧新能源汽車控股有限公司、鴻海集團與騰訊集團透過各自附屬實體聯合創立的創新投資平臺,“互聯網+智慧電動汽車”是該平臺旗下核心戰略專案和獨立企業,旨在開發面向未來的個人出行解決方案,塑造源自中國、佈局世界的高端品牌,為消費者提供智慧、愉悅、生態友好的駕乘體驗。

畢福康博士擁有機械工程學博士學位,是全球電動汽車研發領域的一流專家。畢福康博士此前在寶馬集團總部工作20年,擔任過底盤開發、傳動系統開發及產品戰略等方面的多個高級管理崗位,2010年起擔任寶馬集團新一代電動超級跑車i8項目總監,成為這一世界汽車行業劃時代旗艦車型的研發主腦。通過引入革命性的開發流程,畢福康博士帶領團隊實現了i8車型于2014年成功面世,在產品性能、材料、技術革新及研發速度等各個方面顯著優於傳統汽車產品,創下了全球汽車行業新的標杆。在畢福康博士的推動下,i8車型還引進了創新的銷售和客戶體驗機制。

戴雷博士是中國豪華汽車領域擁有最豐富銷售、運營和品牌塑造經驗的高層主管之一,也是業內公認的“中國通”。戴雷博士此前曾擔任東風英菲尼迪汽車有限公司總經理和華晨寶馬汽車有限公司行銷高級副總裁,相關品牌在其任內均創下豪華車市場的銷售增長紀錄,在品牌塑造和市場行銷方面也創造了若干標杆性的案例。戴雷博士在產品戰略、銷售網路發展和合資企業組建運營方面也擁有深厚的經驗。

畢福康博士和戴雷博士將在就任後與傳媒見面,並就“互聯網+智慧電動汽車”的戰略規劃和企業具體運營資訊與傳媒和公眾溝通。

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

【其他文章推薦】

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

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

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

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

「車和家」產品計畫 中國微信現身

4月16日,中國汽車之家創始人李想在微信朋友圈發文表示,目前個人精力110%投入在車和家上,並首次透露具體的產品計畫:車和家將打造兩款車型來滿足90%的城市需求,一款小而美的SEV,一款大而強的SUV。

3月底,李想的電動車創業項目「車和家」完成A輪融資,並且申報造車資質。李想預計在四年時間裡,打造小而美電動車大概需要2億美元的投入。車和家已經準備好了1億美元,另外的1億美元將通過融資獲得。

李想強調,車和家不是互聯網造車,也不玩互聯網概念,而是標準的新生代汽車品牌。一是自主研發,研發與製造工程師團隊平均汽車行業從業15年以上;二是自主生產,自建常州武進750畝的30萬產能的全鋁工廠,以及配套的BMS與電池廠;三是自己提供銷售與服務,打破4S店高價、低質、效率低的現狀。車和家的目標是像蘋果一樣去創新產品與商業模式,像華為一樣扎扎實實搞研發,並放眼全球市場。

以下為李想微信朋友圈全文(簡體中文,截圖):

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

【其他文章推薦】

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

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

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

9012年了,再不會Https就老了

前言  

       合格的web後端程序員,在開發之外,必須給IIS、Nginx、各種原生web服務器上配置Https,不然你就僅僅是個碼農,本博最近將專題記錄

  • 如何為IIS,Nginx配置Https

  • 如何申請適用於生產的免費SSL證書

本博客小試牛刀,先實操在Nginx for Docker上添加自簽名SSL證書

為啥先倒騰自簽名SSL證書,申請公網SSL證書需要公網可識別的域名或者公網IP;

如果有實際SSL證書, 按照本文替換即可。

手繪Https原理

長話短說:  目前常見的Http請求明文傳輸, 報文可被截取並篡改,請求可被偽造; 

因此基於常見HTTP(HTTP-TCP-IP)協議棧引入SSL/TSL(Transport Secure Layer) ,HTTPS在進行加密傳輸之前會進行一次握手,確定傳輸密鑰。

流程解讀:

① 傳輸密鑰是對稱密鑰,用於雙方對傳輸數據的加解密

② 怎麼在傳輸之前確立傳輸密鑰呢? 針對普遍的多客戶端訪問受信web服務器的場景, 提出非對稱密鑰(公鑰存於客戶端,私鑰存於web服務器),雙方能互相加解密,說明中間數據(傳輸密鑰)沒被篡改。

③ 再拋出疑問,怎麼認定下發的公鑰是這個web服務器匹配的密鑰?怎麼確定這個公鑰下發過程沒被截取篡改? 這就是追溯到握手階段的下發證書過程,瀏覽器內置的CA機構認定該證書是其有效下發,並通過簽名認定該證書沒被篡改,最終認定該 證書下發的公鑰是受信web服務器準確下發。

④ 如果面向面試記憶Https原理,恐怕有些難度,所以個人用一種 【雞生蛋還是蛋生雞】的方式向上追溯流程, 方便大家知其然更知其所以然。

前置準備

 >   CentOS機器上安裝Docker、 Docker-Compose

 >   常規操作構建 Nginx for Docker網站, 項目結構如下: 

ssl-docker-nginx
    ├── docker-compose.yml
    ├── nginx   
    │      └── nginx.conf
    └── site
           └── index.html

    該項目將會使用  nginx/nginx.conf、site/index.html替換Nginx鏡像默認配置文件和默認啟動頁,docker-compose.yml 如下:

version: '2'
services:
  server:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./site:/usr/share/nginx/html
    ports:
    - "8080:80"

    docker-compose  up -d 啟動Nginx容器,還是那樣熟悉的味道: 【chrome默認將http連接認定為不安全

添加SSL自簽名證書

很明顯: web服務器需要存儲證書(內置了公鑰)和私鑰

 ① 創建自簽名證書 (什麼叫自簽名,就是自己給自己頒發 SSL證書)

[nodotnet@gs-server-5809 ssl-docker-nginx]$ openssl req -newkey rsa:2048 -nodes -keyout nginx/my-site.com.key -x509 -days 365out nginx/my-site.com.crt

req是證書請求的子命令,-newkey rsa:2048 -keyout nginx/my-site.com.key表示生成私鑰(PKCS8格式),

          -nodes 表示私鑰不加密,

           -x509表示輸出證書,-days365 為有效期,此後根據提示輸入證書擁有者信息;

       之後會在nginx目錄下生產2個文件, 分別是私鑰、證書

 ② 將證書和密鑰掛載到Nginx Image, 修改docker-compose.yml

version: '2'
services:
  server:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./site:/usr/share/nginx/html
      - ./nginx/my-site.com.crt:/etc/nginx/my-site.com.crt    #新行 - ./nginx/my-site.com.key:/etc/nginx/my-site.com.key    #新行
    ports:
    - "8080:80"
    - "443:443"        // 容器開啟HTTPS默認的443端口

 ③  修改nginx/nginx.conf,接受Https請求

events {
  worker_connections  4096;  ## Default: 1024
}

http {
    server {
        listen 80;
        root         /usr/share/nginx/html/;
    }

    server {            # 新Server接受來自443端口的Https請求
        listen              443 ssl;
        ssl_certificate     /etc/nginx/my-site.com.crt;
        ssl_certificate_key /etc/nginx/my-site.com.key;
        root        /usr/share/nginx/html;
    }
}

執行docker-compose down && docker-compose up -d 發起https://10.201.80.126:443請求,當前自簽名證書頒發機構不在瀏覽器內置的CA機構,所以該證書目前被瀏覽器認為是無效。

理論上將 該自簽名證書導出,之後在 【chrome瀏覽器】-【高級設置】-【管理證書】中導入該證書,即可讓 chrome接受自簽名SSL證書。

That‘s All,  Https作為以後web的主流配置,碼農進階資深必須掌握;後續會記錄Https & HSTS, 申請免費SSL證書,盡請關注。

 

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

【其他文章推薦】

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

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

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

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

OTA升級詳解(三)

君子知夫不全不粹之不足以為美也, 

故誦數以貫之,

思索以通之,

為其人以處之,

除其害者以持養之;

          出自荀子《勸學篇》

終於OTA的升級過程的詳解來了,之前的兩篇文章與主要是鋪墊,

OTA升級的一些基礎知識,那這邊文章就開始揭開OTA-recovery模式升級過程的神秘面紗,需要說明的是

以下重點梳理了本人認為的關鍵、核心的流程,其他如ui部分、簽名校驗部分我並未花筆墨去描述,主要

還是講升級的核心,其他都是枝枝恭弘=叶 恭弘恭弘=叶 恭弘。Android 10 recovery源碼分析,代碼來源路徑:

https://www.androidos.net.cn/android/10.0.0_r6/xref

本文所講的流程代碼路徑為:bootable/recovery/

首先從文件層面說下升級功能的調用流程,說明如下:

recovery-main.cpp      升級的主入口

recovery.cpp                開始recovery升級的處理流程

install/install.cpp         執行升級的處理流程(調用updater)

updater/updater.cpp  完成升級的核心流程

 

 

 

1 主入口代碼為:recovery-main.cpp,main入口

1.1 日誌相關的工作準備

 

 1 // We don't have logcat yet under recovery; so we'll print error on screen and log to stdout
 2 // (which is redirected to recovery.log) as we used to do.
 3 android::base::InitLogging(argv, &UiLogger);
 4 
 5 // Take last pmsg contents and rewrite it to the current pmsg session.
 6 static constexpr const char filter[] = "recovery/";
 7 // Do we need to rotate?
 8 bool do_rotate = false;
 9 
10 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logbasename, &do_rotate);
11 // Take action to refresh pmsg contents
12 __android_log_pmsg_file_read(LOG_ID_SYSTEM, ANDROID_LOG_INFO, filter, logrotate, &do_rotate);
13 
14 time_t start = time(nullptr);
15 
16 // redirect_stdio should be called only in non-sideload mode. Otherwise we may have two logger
17 // instances with different timestamps.
18 redirect_stdio(Paths::Get().temporary_log_file().c_str());

1.2 load_volume_table(); 加載系統分區信息,注意這裏並明白掛載分區

.mount_point = “/tmp”, .fs_type = “ramdisk”, .blk_device = “ramdisk”, .length = 0 

mount_point — 掛載點    fs_type — 分區類型 

blk_device     — 設備塊名 length  — 分區大小

1.3 掛載/cache分區,我們的升級命令都放在這個分區下

1 has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;

1.4 獲取升級的參數並寫BCB塊信息

std::vector<std::string> args = get_args(argc, argv);

if (!update_bootloader_message(options, &err)) {
    LOG(ERROR) << "Failed to set BCB message: " << err;
}

a、讀取misc分區分區,並將recovery模式升級的標記寫到misc分區中,這樣做的目的是斷電續升,

升級中掉電之後,如果下次開機重啟,在bootloader中會讀取此標記,並重新進入到recovery模式中

update_bootloader_message函數完成此功能。

b、從/cache/recovery/command 中讀取升級參數,這裏recovery啟動進程是未帶入參數時,command

文件的接口其實有很詳細的解釋

 * The arguments which may be supplied in the recovery.command file:
 *   --update_package=path - verify install an OTA package file
 *   --wipe_data - erase user data (and cache), then reboot
 *   --prompt_and_wipe_data - prompt the user that data is corrupt, with their consent erase user
 *       data (and cache), then reboot
 *   --wipe_cache - wipe cache (but not user data), then reboot
 *   --show_text - show the recovery text menu, used by some bootloader (e.g. http://b/36872519).
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
 *   --just_exit - do nothing; exit and reboot

1.5 加載recovery_ui_ext.so,完成升級中與屏幕信息的显示,升級進度,升級結果等。這裏就不多說了。

static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
  // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
  // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
  // in on dlclose).
  void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);

  using MakeDeviceType = decltype(&make_device);
  MakeDeviceType make_device_func = nullptr;
  if (librecovery_ui_ext == nullptr) {
    printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
  } else {
    reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
    if (make_device_func == nullptr) {
      printf("Failed to dlsym make_device: %s\n", dlerror());
    }
  }

1.6 非fastboot模式升級就開始了recovery模式升級,start_recovery

ret = fastboot ? StartFastboot(device, args) : start_recovery(device, args);

2 進入 recovery.cpp 

2.1 參數解析,這些參數其實就是來源於/cache/recovery/command, 上面已經通過get_arg,

讀取到了args中

2.2 界面的各種ui信息显示,點事電量的檢查等待輔助動作。

2.3 函數名為安裝升級包,其實還未真正開始進行升級包的安裝

1 status = install_package(update_package, should_wipe_cache, true, retry_count, ui);

2.4 安裝結束之後由finish_recovery()完成收尾工作,保存日誌、清除BCB中的標記,設備重啟。

 1 static void finish_recovery() {
 2   std::string locale = ui->GetLocale();
 3   // Save the locale to cache, so if recovery is next started up without a '--locale' argument
 4   // (e.g., directly from the bootloader) it will use the last-known locale.
 5   if (!locale.empty() && has_cache) {
 6     LOG(INFO) << "Saving locale \"" << locale << "\"";
 7     if (ensure_path_mounted(LOCALE_FILE) != 0) {
 8       LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
 9     } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
10       PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
11     }
12   }
13 
14   copy_logs(save_current_log, has_cache, sehandle);
15 
16   // Reset to normal system boot so recovery won't cycle indefinitely.
17   std::string err;
18   if (!clear_bootloader_message(&err)) {
19     LOG(ERROR) << "Failed to clear BCB message: " << err;
20   }
21 
22   // Remove the command file, so recovery won't repeat indefinitely.
23   if (has_cache) {
24     if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
25       LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
26     }
27     ensure_path_unmounted(CACHE_ROOT);
28   }
29 
30   sync();  // For good measure.
31 }

3 install/install.cpp

3.1 install.cpp其實就進入了安裝升級包的準備動作,剛上的install_package,是假的,這裏才是

really_install_package

1 really_install_package(path, &updater_wipe_cache, needs_mount, &log_buffer,
2                                     retry_count, &max_temperature, ui);

3.2 really_install_package 關鍵地方已加註釋

 1 static int really_install_package(const std::string& path, bool* wipe_cache, bool needs_mount,
 2                                   std::vector<std::string>* log_buffer, int retry_count,
 3                                   int* max_temperature, RecoveryUI* ui) {
 4   ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
 5   ui->Print("Finding update package...\n");
 6   // Give verification half the progress bar...
 7   ui->SetProgressType(RecoveryUI::DETERMINATE);
 8   ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
 9   LOG(INFO) << "Update location: " << path;
10 
11   // Map the update package into memory.
12   ui->Print("Opening update package...\n");
13 
14   if (needs_mount) {
15     if (path[0] == '@') {
16       ensure_path_mounted(path.substr(1));
17     } else {
18       ensure_path_mounted(path);
19     }
20   }
21 
22   /* 將zip映射到內存中 */
23   auto package = Package::CreateMemoryPackage(
24       path, std::bind(&RecoveryUI::SetProgress, ui, std::placeholders::_1));
25   if (!package) {
26     log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
27     return INSTALL_CORRUPT;
28   }
29 
30   // Verify package.進行zip包進行簽名校驗
31   if (!verify_package(package.get(), ui)) {
32     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
33     return INSTALL_CORRUPT;
34   }
35 
36   // Try to open the package.打開zip包
37   ZipArchiveHandle zip = package->GetZipArchiveHandle();
38   if (!zip) {
39     log_buffer->push_back(android::base::StringPrintf("error: %d", kZipOpenFailure));
40     return INSTALL_CORRUPT;
41   }
42 
43   // Additionally verify the compatibility of the package if it's a fresh install.
44   if (retry_count == 0 && !verify_package_compatibility(zip)) {
45     log_buffer->push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure));
46     return INSTALL_CORRUPT;
47   }
48 
49   // Verify and install the contents of the package.
50   ui->Print("Installing update...\n");
51   if (retry_count > 0) {
52     ui->Print("Retry attempt: %d\n", retry_count);
53   }
54   ui->SetEnableReboot(false);
55   int result =
56       /* 執行升級updater進程進行升級 */
57       try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature, ui);
58   ui->SetEnableReboot(true);
59   ui->Print("\n");
60 
61   return result;
62 }

3.3 try_update_binary

從升級包中讀取元數據信息

1 ReadMetadataFromPackage(zip, &metadata)

3.4 從升級包中讀取updater進程

 1 int SetUpNonAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int retry_count,
 2                              int status_fd, std::vector<std::string>* cmd) {
 3   CHECK(cmd != nullptr);
 4 
 5   // In non-A/B updates we extract the update binary from the package.
 6   static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
 7   ZipString binary_name(UPDATE_BINARY_NAME);
 8   ZipEntry binary_entry;
 9   if (FindEntry(zip, binary_name, &binary_entry) != 0) {
10     LOG(ERROR) << "Failed to find update binary " << UPDATE_BINARY_NAME;
11     return INSTALL_CORRUPT;
12   }
13 
14   const std::string binary_path = Paths::Get().temporary_update_binary();
15   unlink(binary_path.c_str());
16   android::base::unique_fd fd(
17       open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755));
18   if (fd == -1) {
19     PLOG(ERROR) << "Failed to create " << binary_path;
20     return INSTALL_ERROR;
21   }
22 
23   int32_t error = ExtractEntryToFile(zip, &binary_entry, fd);
24   if (error != 0) {
25     LOG(ERROR) << "Failed to extract " << UPDATE_BINARY_NAME << ": " << ErrorCodeString(error);
26     return INSTALL_ERROR;
27   }
28 
29   // When executing the update binary contained in the package, the arguments passed are:
30   //   - the version number for this interface
31   //   - an FD to which the program can write in order to update the progress bar.
32   //   - the name of the package zip file.
33   //   - an optional argument "retry" if this update is a retry of a failed update attempt.
34   *cmd = {
35     binary_path,
36     std::to_string(kRecoveryApiVersion),
37     std::to_string(status_fd),
38     package,
39   };
40   if (retry_count > 0) {
41     cmd->push_back("retry");
42   }
43   return 0;
44 }

3.5 創建管道,這裏子進程關閉了讀端,父進程關閉了寫端,這樣就是保證從單向的信息通信,從

子進程傳入信息到父進程中。

1 android::base::Pipe(&pipe_read, &pipe_write, 0)

3.6 創建子進程,在子進程中運行update-binary進程

 1 if (pid == 0) {
 2     umask(022);
 3     pipe_read.reset();
 4 
 5     // Convert the std::string vector to a NULL-terminated char* vector suitable for execv.
 6     auto chr_args = StringVectorToNullTerminatedArray(args);
 7     /* chr_args[0] 其實就是升級包中的 META-INF/com/google/android/update-binary */
 8     execv(chr_args[0], chr_args.data());
 9     // We shouldn't use LOG/PLOG in the forked process, since they may cause the child process to
10     // hang. This deadlock results from an improperly copied mutex in the ui functions.
11     // (Bug: 34769056)
12     fprintf(stdout, "E:Can't run %s (%s)\n", chr_args[0], strerror(errno));
13     _exit(EXIT_FAILURE);
14   }

3.7 recovery獲取子進程的信息並显示,進度、ui_print 等等。

1 FILE* from_child = android::base::Fdopen(std::move(pipe_read), "r");
2 while (fgets(buffer, sizeof(buffer), from_child) != nullptr)

4 execv執行升級進程之後,工作在updater/updater.cpp中完成。

4.1 這裏的主要核心就是構造腳本解析器對updater-script中的命令進行執行,至於這個腳本解析器

是如何構造的,如何執行的, 其實我也搞的不是很清楚。

4.2 安裝升級包的核心程序就是Configure edify’s functions. 中的那些註冊回調函數

  1 int main(int argc, char** argv) {
  2 // Various things log information to stdout or stderr more or less
  3 // at random (though we've tried to standardize on stdout).  The
  4 // log file makes more sense if buffering is turned off so things
  5 // appear in the right order.
  6   setbuf(stdout, nullptr);
  7   setbuf(stderr, nullptr);
  8 // We don't have logcat yet under recovery. Update logs will always be written to stdout
  9 // (which is redirected to recovery.log).
 10   android::base::InitLogging(argv, &UpdaterLogger);
 11 if (argc != 4 && argc != 5) {
 12     LOG(ERROR) << "unexpected number of arguments: " << argc;
 13 return 1;
 14   }
 15 /* 支持的版本檢查 */
 16 char* version = argv[1];
 17 if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') {
 18 // We support version 1, 2, or 3.
 19     LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1];
 20 return 2;
 21   }
 22 // Set up the pipe for sending commands back to the parent process.
 23 int fd = atoi(argv[2]);
 24   FILE* cmd_pipe = fdopen(fd, "wb");
 25   setlinebuf(cmd_pipe);
 26 // Extract the script from the package.
 27 /* 從包中提取腳本 */
 28 const char* package_filename = argv[3];
 29   MemMapping map;
 30 if (!map.MapFile(package_filename)) {
 31     LOG(ERROR) << "failed to map package " << argv[3];
 32 return 3;
 33   }
 34   ZipArchiveHandle za;
 35 int open_err = OpenArchiveFromMemory(map.addr, map.length, argv[3], &za);
 36 if (open_err != 0) {
 37     LOG(ERROR) << "failed to open package " << argv[3] << ": " << ErrorCodeString(open_err);
 38     CloseArchive(za);
 39 return 3;
 40   }
 41 ZipString script_name(SCRIPT_NAME);
 42   ZipEntry script_entry;
 43 int find_err = FindEntry(za, script_name, &script_entry);
 44 if (find_err != 0) {
 45     LOG(ERROR) << "failed to find " << SCRIPT_NAME << " in " << package_filename << ": "
 46                << ErrorCodeString(find_err);
 47     CloseArchive(za);
 48 return 4;
 49   }
 50 std::string script;
 51   script.resize(script_entry.uncompressed_length);
 52 int extract_err = ExtractToMemory(za, &script_entry, reinterpret_cast<uint8_t*>(&script[0]),
 53                                     script_entry.uncompressed_length);
 54 if (extract_err != 0) {
 55     LOG(ERROR) << "failed to read script from package: " << ErrorCodeString(extract_err);
 56     CloseArchive(za);
 57 return 5;
 58   }
 59 // Configure edify's functions.
 60 /* 註冊updater-script中的回調函數 這裏主要是一些斷言函數 abort assert*/
 61   RegisterBuiltins();
 62 /* 這裏主要是一些安裝升級包的函數 主要是對有文件系統的分區來說*/
 63   RegisterInstallFunctions();
 64 /* 這裏主要註冊對裸分區進行升級的函數 */
 65   RegisterBlockImageFunctions();
 66   RegisterDynamicPartitionsFunctions();
 67   RegisterDeviceExtensions();
 68 // Parse the script.
 69 std::unique_ptr<Expr> root;
 70 int error_count = 0;
 71 int error = ParseString(script, &root, &error_count);
 72 if (error != 0 || error_count > 0) {
 73     LOG(ERROR) << error_count << " parse errors";
 74     CloseArchive(za);
 75 return 6;
 76   }
 77   sehandle = selinux_android_file_context_handle();
 78   selinux_android_set_sehandle(sehandle);
 79 if (!sehandle) {
 80 fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
 81   }
 82 // Evaluate the parsed script.
 83   UpdaterInfo updater_info;
 84   updater_info.cmd_pipe = cmd_pipe;
 85   updater_info.package_zip = za;
 86   updater_info.version = atoi(version);
 87   updater_info.package_zip_addr = map.addr;
 88   updater_info.package_zip_len = map.length;
 89 State state(script, &updater_info);
 90 if (argc == 5) {
 91 if (strcmp(argv[4], "retry") == 0) {
 92       state.is_retry = true;
 93     } else {
 94 printf("unexpected argument: %s", argv[4]);
 95     }
 96   }
 97 std::string result;
 98 bool status = Evaluate(&state, root, &result);
 99 if (!status) {
100 if (state.errmsg.empty()) {
101       LOG(ERROR) << "script aborted (no error message)";
102 fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
103     } else {
104       LOG(ERROR) << "script aborted: " << state.errmsg;
105 const std::vector<std::string> lines = android::base::Split(state.errmsg, "\n");
106 for (const std::string& line : lines) {
107 // Parse the error code in abort message.
108 // Example: "E30: This package is for bullhead devices."
109 if (!line.empty() && line[0] == 'E') {
110 if (sscanf(line.c_str(), "E%d: ", &state.error_code) != 1) {
111             LOG(ERROR) << "Failed to parse error code: [" << line << "]";
112           }
113         }
114 fprintf(cmd_pipe, "ui_print %s\n", line.c_str());
115       }
116     }
117 // Installation has been aborted. Set the error code to kScriptExecutionFailure unless
118 // a more specific code has been set in errmsg.
119 if (state.error_code == kNoError) {
120       state.error_code = kScriptExecutionFailure;
121     }
122 fprintf(cmd_pipe, "log error: %d\n", state.error_code);
123 // Cause code should provide additional information about the abort.
124 if (state.cause_code != kNoCause) {
125 fprintf(cmd_pipe, "log cause: %d\n", state.cause_code);
126 if (state.cause_code == kPatchApplicationFailure) {
127         LOG(INFO) << "Patch application failed, retry update.";
128 fprintf(cmd_pipe, "retry_update\n");
129       } else if (state.cause_code == kEioFailure) {
130         LOG(INFO) << "Update failed due to EIO, retry update.";
131 fprintf(cmd_pipe, "retry_update\n");
132       }
133     }
134 if (updater_info.package_zip) {
135       CloseArchive(updater_info.package_zip);
136     }
137 return 7;
138   } else {
139 fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result.c_str());
140   }
141 if (updater_info.package_zip) {
142     CloseArchive(updater_info.package_zip);
143   }
144 return 0;
145 }

以上就是基於Android的OTARecovery模式升級流程。我這裏主要是梳理整個升級流程的主要,

很多地方還是寫的不夠細,望讀者理解,我認為比較核心與關鍵的地方有以下幾點吧

  • 主系統與recovery升級系統,升級消息的傳遞通過cache;
  • BCB塊中寫信息來保證斷電續升;
  • 主系統中fork子進程進行升級進程的執行,並通過pipe管道進行信息交互;
  • updater中使用命令與執行的分離,命令在updater-script中,執行在update-binary中;
    • 升級程序通過升級包帶入的,那麼核心升級流程是每次都有機會變更或者優化的,
    • 這樣就比那些將升級流程預置在系統中的要靈活的很多;

 

                                     

 

 

        長按二維碼關注【嵌入式C部落】,獲取更多編程資料及精華文章

 

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

【其他文章推薦】

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

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

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

RocketMQ ACL使用指南

目錄

@(本節目錄)

1、什麼是ACL?

ACL是access control list的簡稱,俗稱訪問控制列表。訪問控制,基本上會涉及到用戶、資源、權限、角色等概念,那在RocketMQ中上述會對應哪些對象呢?

  • 用戶
    用戶是訪問控制的基礎要素,也不難理解,RocketMQ ACL必然也會引入用戶的概念,即支持用戶名、密碼。
  • 資源
    資源,需要保護的對象,在RocketMQ中,消息發送涉及的Topic、消息消費涉及的消費組,應該進行保護,故可以抽象成資源。
  • 權限
    針對資源,能進行的操作,
  • 角色
    RocketMQ中,只定義兩種角色:是否是管理員。

另外,RocketMQ還支持按照客戶端IP進行白名單設置。

2、ACL基本流程圖

在講解如何使用ACL之前,我們先簡單看一下RocketMQ ACL的請求流程:

對於上述具體的實現,將在後續文章中重點講解,本文的目的只是希望給讀者一個大概的了解。

3、如何配置ACL

3.1 acl配置文件

acl默認的配置文件名:plain_acl.yml,需要放在${ROCKETMQ_HOME}/store/config目錄下。下面對其配置項一一介紹。

3.1.1 globalWhiteRemoteAddresses

全局白名單,其類型為數組,即支持多個配置。其支持的配置格式如下:


  • 表示不設置白名單,該條規則默認返回false。
  • “*”
    表示全部匹配,該條規則直接返回true,將會阻斷其他規則的判斷,請慎重使用。
  • 192.168.0.{100,101}
    多地址配置模式,ip地址的最後一組,使用{},大括號中多個ip地址,用英文逗號(,)隔開。
  • 192.168.1.100,192.168.2.100
    直接使用,分隔,配置多個ip地址。
  • 192.168..或192.168.100-200.10-20
    每個IP段使用 “*” 或”-“表示範圍。

3.1.2 accounts

配置用戶信息,該類型為數組類型。擁有accessKey、secretKey、whiteRemoteAddress、admin、defaultTopicPerm、defaultGroupPerm、topicPerms、groupPerms子元素。

3.1.2.1 accessKey

登錄用戶名,長度必須大於6個字符。

3.1.2.2 secretKey

登錄密碼。長度必須大於6個字符。

3.1.2.3 whiteRemoteAddress

用戶級別的IP地址白名單。其類型為一個字符串,其配置規則與globalWhiteRemoteAddresses,但只能配置一條規則。

3.1.2.4 admin

boolean類型,設置是否是admin。如下權限只有admin=true時才有權限執行。

  • UPDATE_AND_CREATE_TOPIC
    更新或創建主題。
  • UPDATE_BROKER_CONFIG
    更新Broker配置。
  • DELETE_TOPIC_IN_BROKER
    刪除主題。
  • UPDATE_AND_CREATE_SUBSCRIPTIONGROUP
    更新或創建訂閱組信息。
  • DELETE_SUBSCRIPTIONGROUP
    刪除訂閱組信息。
3.1.2.5 defaultTopicPerm

默認topic權限。該值默認為DENY(拒絕)。

3.1.2.6 defaultGroupPerm

默認消費組權限,該值默認為DENY(拒絕),建議值為SUB。

3.1.2.7 topicPerms

設置topic的權限。其類型為數組,其可選擇值在下節介紹。

3.1.2.8 groupPerms

設置消費組的權限。其類型為數組,其可選擇值在下節介紹。可以為每一消費組配置不一樣的權限。

3.2 RocketMQ ACL權限可選值

  • DENY
    拒絕。
  • PUB
    擁有發送權限。
  • SUB
    擁有訂閱權限。

3.3、權限驗證流程

上面定義了全局白名單、用戶級別的白名單,用戶級別的權限,為了更好的配置ACL權限規則,下面給出權限匹配邏輯。

4、使用示例

4.1 Broker端安裝

首先,需要在broker.conf文件中,增加參數aclEnable=true。並拷貝distribution/conf/plain_acl.yml文件到${ROCKETMQ_HOME}/conf目錄。

broker.conf的配置文件如下:

brokerClusterName = DefaultCluster
brokerName = broker-b
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
listenPort=10915
storePathRootDir=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store
storePathCommitLog=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store/commitlog
namesrvAddr=127.0.0.1:9876
autoCreateTopicEnable=false
aclEnable=true

plain_acl.yml文件內容如下:

globalWhiteRemoteAddresses:

accounts:
- accessKey: RocketMQ
  secretKey: 12345678
  whiteRemoteAddress:
  admin: false
  defaultTopicPerm: DENY
  defaultGroupPerm: SUB
  topicPerms:
  - TopicTest=PUB
  groupPerms:
  # the group should convert to retry topic
  - oms_consumer_group=DENY

- accessKey: admin
  secretKey: 12345678
  whiteRemoteAddress:
  # if it is admin, it could access all resources
  admin: true

從上面的配置可知,用戶RocketMQ只能發送TopicTest的消息,其他topic無權限發送;拒絕oms_consumer_group消費組的消息消費,其他消費組默認可消費。

4.2 消息發送端示例

public class AclProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name", getAclRPCHook());
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        for (int i = 0; i < 1; i++) {
            try {
                Message msg = new Message("TopicTest3" ,"TagA" , ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

運行效果如圖所示:

4.3 消息消費端示例

public class AclConsumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4", getAclRPCHook(),new AllocateMessageQueueAveragely());
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

發現並不沒有消費消息,符合預期。

關於RocketMQ ACL的使用就介紹到這裏了,下一篇將介紹RocketMQ ACL實現原理。

推薦閱讀:
1、

2、

3、

4、

作者介紹:
丁威,《RocketMQ技術內幕》作者,RocketMQ 社區佈道師,公眾號: 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。

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

【其他文章推薦】

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

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

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

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

canvas繪製工作流之繪製節點

   上一篇我們介紹了canvas繪製工作流的大概步驟,接下來會有系列文章細緻的介紹怎麼用canvas繪製工作流;這篇文章主要介紹用canvas繪製流程節點。

  繪製前我們需要先準備一張節點圖片,例如:;好了,正題開始:

  1. html中添加canvas標籤:
<canvas id="canvasId" width = "800" height="600" style="border:1px solid black;  margin-left: 1px;"></canvas>

這裏要注意設置canvas標籤的寬度跟高度,也就是要設置畫布的寬度跟高度。

  1. 獲取畫布對象並初始化畫布參數
var _canvas= document.getElementById(“canvasId”);

var _height = _canvas.height;//獲取畫布高度

var _width = _canvas.width;//獲取畫布寬度

Var ctx =_canvas.getContext('2d');

//畫個畫布大小的長方形,目的是為了有個好看的小邊框框
ctx.clearRect(0, 0, _width, _height);

/*繪製畫布的背景線*/
//設置線寬
ctx.lineWidth  = 0.1;
//繪製縱向背景線
for(var i = 1; i < _width / 15; i++) {
  ctx.beginPath();
  ctx.moveTo(i * 15, 0);
  ctx.lineTo(i * 15, _height);
  ctx.stroke();
}
//繪製橫向背景線
for(var i = 1; i < _ height / 15; i++) {
  ctx.beginPath();
  ctx.moveTo(0, i * 15);
  ctx.lineTo(_width, i * 15);
  ctx.stroke();
}

 

繪製完效果如圖:

  1. 獲取節點圖片對象
     //創建新的圖片對象
    
     var _img = new Image();
    
      //指定圖片的URL
    
     _img.src="node.png";

            我這裏為了舉個例子直接創建圖片對象,實際繪製過程中可以直接獲取圖片對象,因為動態創建圖片對象是有個圖片加載的時間。

  1. 繪製節點圖片
ctx.drawImage(_img,_x,_y,_imgWidth, _imgHeight);

    這裏_img是上面獲取到的圖片對象,_x是圖片要繪製在畫布中的x坐標,_y是圖片要繪製在畫布中的_y坐標,_imgWidth是要將圖片繪製的寬度,_imgHeight是要將 圖片繪製的寬度。

    實際應用過程中,一般都會當去鼠標的位置當做x坐標跟y坐標,具體的後面文章會介紹到。

       繪製的效果圖:

   節點下面的文字後面文章會詳細講到怎麼繪製。

  每天get一點點,每天成長一點點,好了,今天就到這裏。

        

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

【其他文章推薦】

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

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

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

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

Abp vNext 自定義 Ef Core 倉儲引發異常

問題

在使用自定義 Ef Core 倉儲和 ABP vNext 注入的默認倉儲時,通過兩個 Repository 進行 Join 操作,提示 Cannot use multiple DbContext instances within a single query execution. Ensure the query uses a single context instance. 。這個異常信息翻譯成中文的大概意思就是,你不能使用兩個 DbContext 裏面的 DbSet 進行 Join 查詢。

如果將自定義倉儲改為 IRepository<TEntity,TKey> 進行注入,是可以與 _courseRepostory 進行關聯查詢的。

我在 XXXEntityFrameworkCoreModule 的配置,以及自定義倉儲 EfCoreStudentRepository 代碼如下。

XXXEntityFrameworkCoreModule 代碼:

public class XXXEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<XXXDbContext>(op =>
        {
            op.AddDefaultRepositories();
        });
        
        Configure<AbpDbContextOptions>(op => op.UsePostgreSql());
    }
}

EfCoreStudentRepository 代碼:

public class EfCoreStudentRepository : EfCoreRepository<IXXXDbContext, Student, long>, IStudentRepository
{
    public EfCoreStudentRepository(IDbContextProvider<IXXXDbContext> dbContextProvider) : base(dbContextProvider)
    {
    }

    public Task<int> GetCountWithStudentlIdAsync(long studentId)
    {
        return DbSet.CountAsync(x=>x.studentId == studentId);
    }
}

原因

原因在異常信息已經說得十分清楚了,這裏我們需要了解兩個問題。

  1. 什麼原因導致兩個倉儲內部的 DbContext 不一致?
  2. 為什麼 ABP vNext 自己實現的倉儲能夠進行關聯查詢呢?

首先我們得知道,倉儲內部的 DbContext是怎麼獲取的。我們的自定義倉儲都會繼承 EfCoreRepository ,而這個倉儲是實現了 IQuerable<T> 接口的,最終它會通過一個 IDbContextProvider<TDbContext> 獲得一個可用的 DbContext

public class EfCoreRepository<TDbContext, TEntity> : RepositoryBase<TEntity>, IEfCoreRepository<TEntity>
    where TDbContext : IEfCoreDbContext
    where TEntity : class, IEntity
{
    public virtual DbSet<TEntity> DbSet => DbContext.Set<TEntity>();

    DbContext IEfCoreRepository<TEntity>.DbContext => DbContext.As<DbContext>();

    // 這裏可以看到,是通過 IDbContextProvider 來獲得 DbContext 的。
    protected virtual TDbContext DbContext => _dbContextProvider.GetDbContext();

    protected virtual AbpEntityOptions<TEntity> AbpEntityOptions => _entityOptionsLazy.Value;

    private readonly IDbContextProvider<TDbContext> _dbContextProvider;
    private readonly Lazy<AbpEntityOptions<TEntity>> _entityOptionsLazy;

    // ... 其他代碼。
}

下面就是 IDbContextProvider<TDbContext> 內部的核心代碼:

public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> where TDbContext : IEfCoreDbContext
{
    private readonly IUnitOfWorkManager _unitOfWorkManager;
    private readonly IConnectionStringResolver _connectionStringResolver;

    // ... 其他代碼。

    public TDbContext GetDbContext()
    {
        var unitOfWork = _unitOfWorkManager.Current;
        if (unitOfWork == null)
        {
            throw new AbpException("A DbContext can only be created inside a unit of work!");
        }

        var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
        var connectionString = _connectionStringResolver.Resolve(connectionStringName);

        // 會構造一個 Key,而這個 Key 剛好是泛型類型的 FullName。
        var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";

        // 內部是從一個字典當中,根據 dbContextKey 獲取 DbContext。如果不存在的話則調用工廠方法創建一個新的 DbContext。
        var databaseApi = unitOfWork.GetOrAddDatabaseApi(
            dbContextKey,
            () => new EfCoreDatabaseApi<TDbContext>(
                CreateDbContext(unitOfWork, connectionStringName, connectionString)
            ));

        return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext;
    }

    // ... 其他代碼。
}

通過以上代碼我們就可以知道,ABP vNext 在倉儲的內部是通過 IDbContextProvider<TDbContext> 中的 TDbContext 泛型,來確定是否構建一個新的 DbContext 對象。

不論是 ABP vNext 針對 IRepository<TEntity,TKey> ,還是我們自己實現的自定義倉儲,它們最終的實現都是基於 EfCoreRepository<TDbContext,TEntity,TKey> 的。而我們 IDbContextProvider<TDbContext> 的泛型,也是這個倉儲基類提供的,後者的 TDbContext 就是前者的泛型參數。

所以當我們在模塊添加 DbContext 的過城中,只要調用了 AddDefaultRepositories() 方法,ABP vNext 就會遍歷你提供的 TDbContext 所定義的實體,然後為這些實體建立默認的倉儲。

在注入倉儲的時候,找到了獲得默認倉儲實現類型的方法,可以看到這裏它使用的是 DefaultRepositoryDbContextType 作為默認的 TDbContext 類型。

protected virtual Type GetDefaultRepositoryImplementationType(Type entityType)
{
    var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType);

    // 重點在於構造倉儲類型時,傳遞的 Options.DefaultRepositoryDbContextType 參數,這個參數就是後面 EfCoreRepository 的 TDbContext 泛型。
    if (primaryKeyType == null)
    {
        return Options.SpecifiedDefaultRepositoryTypes
            ? Options.DefaultRepositoryImplementationTypeWithoutKey.MakeGenericType(entityType)
            : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType);
    }

    return Options.SpecifiedDefaultRepositoryTypes
        ? Options.DefaultRepositoryImplementationType.MakeGenericType(entityType, primaryKeyType)
        : GetRepositoryType(Options.DefaultRepositoryDbContextType, entityType, primaryKeyType);
}

最後我發現這個就是在模塊調用 AddAbpContext<TDbContext> 所提供的泛型參數。

public abstract class AbpCommonDbContextRegistrationOptions : IAbpCommonDbContextRegistrationOptionsBuilder
{
    // ... 其他代碼

    protected AbpCommonDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
    {
        OriginalDbContextType = originalDbContextType;
        Services = services;
        DefaultRepositoryDbContextType = originalDbContextType;
        CustomRepositories = new Dictionary<Type, Type>();
        ReplacedDbContextTypes = new List<Type>();
    }

    // ... 其他代碼
}

public class AbpDbContextRegistrationOptions : AbpCommonDbContextRegistrationOptions, IAbpDbContextRegistrationOptionsBuilder
{
    public Dictionary<Type, object> AbpEntityOptions { get; }

    public AbpDbContextRegistrationOptions(Type originalDbContextType, IServiceCollection services)
        : base(originalDbContextType, services) // 之類調用的就是上面的構造方法。
    {
        AbpEntityOptions = new Dictionary<Type, object>();
    }
}

public static class AbpEfCoreServiceCollectionExtensions
{
    public static IServiceCollection AddAbpDbContext<TDbContext>(
        this IServiceCollection services, 
        Action<IAbpDbContextRegistrationOptionsBuilder> optionsBuilder = null)
        where TDbContext : AbpDbContext<TDbContext>
    {
        // ... 其他代碼。
        
        var options = new AbpDbContextRegistrationOptions(typeof(TDbContext), services);

        // ... 其他代碼。

        return services;
    }
}

所以,我們的默認倉儲的 dbContextKeyXXXDbContext,我們的自定義倉儲繼承 EfCoreRepository<IXXXDbContext,TEntity,TKey> ,所以它的 dbContextKey 就是 IXXXDbContext 。所以自定義倉儲獲取到的 DbContext 就與自定義倉儲的不一致了,從而提示上述異常。

解決

找到自定自定義倉儲的定義,修改它 EfCoreReposiotry<TDbContext,TEntity,TKey>TDbContext 泛型參數,變更為 XXXDbContext 即可。

public class EfCoreStudentRepository : EfCoreRepository<XXXDbContext, Student, long>, IStudentRepository
{
    public EfCoreStudentRepository(IDbContextProvider<XXXDbContext> dbContextProvider) : base(dbContextProvider)
    {
    }

    public Task<int> GetCountWithStudentlIdAsync(long studentId)
    {
        return DbSet.CountAsync(x=>x.studentId == studentId);
    }
}

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

【其他文章推薦】

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

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

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

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

澳洲野火不止 維州熱浪祭禁火令 擬照辦跨年煙火秀

摘錄自2019年12月30日中央通訊社報導

澳洲野火肆虐,重創當地生態環境及空氣品質,已逾26萬人連署,建議取消知名的雪梨跨年煙火秀,以免助長空氣污染,把錢省下來幫助受災農民及消防員等,不過,雪梨市長克洛弗.摩爾(Clover Moore)已煙火秀照舊,且據報導,高達10萬支煙火都已裝船開運,其中有8成來自中國。

綜合媒體報導,雪梨是全球最早迎接新年的大城市之一,跨年煙火秀更是全球慶祝跨年的重點戲之一,去(2018)年就花了580萬澳元(約1.2億元台幣),今年打算花650萬澳元(約1.36億台幣)來辦跨年煙火秀,但今年正值澳洲發生數十年來最嚴重的野火災難,致農損嚴重,截至目前並未撲滅,致使澳洲民眾在網上連署,建議當局取消今年跨年煙火秀。

不過當局並不打算取消,女市長摩爾並表示,若是取消,可能重創雪梨商家們,也會毀了數以萬計前來雪梨參加跨年活動人們的計劃。

即使如此,這場煙火秀是否在最後喊停,還在未定之天,因為,澳洲野火未停,仍有接近100個火場火勢仍未撲滅,雪梨所屬的新南威爾斯省的災情最嚴重,且位於雪梨西南面的巴爾莫勒爾鎮幾乎全毀,許多公路仍然封閉;且產生的空氣污染已經形成有毒煙霧,籠罩雪梨及其他主要城市。

由於East Gippsland的森林火災不斷升級,加上熱浪來襲,預計今天氣溫可能升高到44度,維多利亞州當局已在昨天(29日)下令,全面撤離Goongerah和Martins Creek地區居民,今天更將全州實行禁火令,嚴禁人們在戶外用火。

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

【其他文章推薦】

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

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

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

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

印度市政府開「垃圾餐廳 」 塑膠撿越多 吃得越好

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

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

【其他文章推薦】

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

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

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

以色列電動車快速充電技術 僅需 5 分鐘充飽電

以色列初創公司 StoreDot 曾研發 30 秒快速手機充電技術,最近 StoreDot 將這項技術延伸至電動車電池上。StoreDot 研發的汽車版電池,只需 5 分鐘就能充滿,跟在油站入油分別不大。   專為電動車研發的 FlashBattery 電池可算是 StoreDot 手機電池的放大版,利用比傳統鋰離子科技更安全和穩定的有機化合物製作。FlashBattery 的最大好處是在極短時間充滿之餘,同時擁有較長的壽命。StoreDot 表示 FlashBattery 可以充電和放電過千次,比起鋰電池多三倍。   這款高效電池仍在研發中,在推出車用電池之前,StoreDot 會在今年年底前正式推出手機版,以電動車版電池原型預計要到明年底才會現身。

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

【其他文章推薦】

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

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

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

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