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 是電子產業重要的元件?

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

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

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

2015節能與新能源汽車產業發展成果彙報及展示會-中國國際汽車新能源及技術應用展覽會

時間:2015年10月21-24日   地點:北京•國家會議中心

【主辦單位】

中華人民共和國工業和信息化部
中華人民共和國財政部
中華人民共和國科學技術部
中華人民共和國發展和改革委員會

【協辦單位】

中國汽車工業協會
中國汽車技術研究中心
中國汽車工程學會
中國汽車工程研究院股份有限公司
中國電工技術學會
中國國際貿易促進委員會機械行業分會
中國國際貿易促進委員會汽車行業分會
汽車知識雜誌社
寰球時代汽車投資管理(北京)有限公司

【承辦單位】北京中汽四方會展有限公司

【展覽合作單位】北京盛大超越國際展覽有限公司

展出面積:35,000平方米(預計)
觀眾數量:60,000人次(預計)
展覽週期:每年一屆,2013年首屆

“成果彙報及展示會”主要由會議和展會兩大部分構成。會議方面主要包括節能與新能源汽車發展成果彙報會、節能與新能源汽車技術研討會、電動汽車國際標準研討會、新能源汽車專利成果研討會和示範城市經驗交流會等內容。展會包括節能與新能源汽車發展情況回顧、節能與新能源汽車產業發展規劃成果展、中國國際汽車新能源及技術應用展覽會、節能與新能源汽車發展前景展望。在前兩屆成果展成功舉辦基礎上,2015年展會將分設整車展區、關鍵零部件展區、充電設施展區和試點城市展區,在重點展示25個創新工程項目研究成果基礎上,全面展示節能與新能源汽車產業發展的最新成果。展會同期還將舉辦試乘試駕等體驗活動。

【參展範圍】
節能汽車、純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類燃料車及其他代用燃料車;
先進內燃機、高效變速器、整車優化設計等節能技術和產品;
電池,燃料電池,電池管理系統,電池的回收和包裝;
電力電容器,飛輪,能源管理系統;
電機,電機保護與控制技術;
充電器及充電站設備及相關配套專案企業、機構;
電動車及其他替代能源汽車的零部件及零部件總成;
加氣設備,儲運設備及技術;
網路管理,可再生能源發電,新型元器件和材料,輕量化;
智慧社區和電網,汽車共用和通訊服務;
檢測,維修,監控,實驗,安全防護裝備;
城市推廣應用示範展示、製造設備、工具及媒體等.

【上屆回顧】

10月21日,為期五天的以“選擇•行動——未來從現在開始”為主題的2014 中國國際汽車新能源及技術應用展暨節能與新能源汽車產業發展規劃成果展覽會(以下簡稱節能與新能源汽車成果展)在北京國家會議中心成功閉幕,展會共吸引來自國內外6萬餘人次的觀展,中央電視臺、北京電視臺、旅遊衛視、中國教育電視臺、新華社、美通社、經濟日報、新浪、搜狐等百餘家中外合作媒體競相報導。

本屆展會得到了中國國家各部委領導的大力指導和支持,10月18日下午,在工業和信息化部相關司局、行業機構領導的陪同下,苗圩部長蒞臨展會表示祝賀並參觀指導,他認真參觀了一汽集團、東風集團、長安集團、北汽集團、豐田汽車、上海通用、吉利汽車、富豪汽車、安凱客車、宇通客車等展臺,詳細瞭解節能與新能源汽車技術研發進程與成果,對企業在節能與新能源汽車發展過程所做的工作、取得的成果和成績給予充分肯定,對行業今後的發展指明方向。他指出,今年的展會在上一屆車展基礎上有較大提升,鼓勵主辦方繼續努力,積極做好明年車展的各項籌備工作,力爭打造品牌展會,為推動我國節能與新能源汽車產業的發展做出積極的貢獻。

中國機械聯合會會長王瑞祥,國家能源委專家諮詢委員會主任、原國家能源局局長張國寶,原機械工業部副部長、重慶市原市委書記張德鄰,原機械工業部副部長、中國電工技術學會理事長孫昌基,原外經貿部部長助理、國家機電產品進口審查辦公室主任徐秉金等領導和專家及總裝備部、國防科工委、商務部、國資委、質檢總局、交通部、環保部等有關單位領導也蒞臨參觀。合肥、哈爾濱、深圳、北京、上海、青島等一批新能源汽車示範推廣應用城市相關負責人,以及三十多家城市政府採購相關領導蒞臨展會並與相關企業進行了合作交流和洽談。

本屆車展還吸引了大批外賓參觀,日本、白俄羅斯、歐洲、美國等地專業觀眾專程前來參觀,千里達和多巴哥駐華大使錢德拉達思•辛格、波蘭駐華大使館、德國駐華大使館等相關人員也蒞臨展會。

對話行業專家  探究新能源汽車產業未來

作為本屆車展最為重要的同期活動,10月17日召開了2014中國節能與新能源汽車產業發展高峰論壇。論壇以“選擇•行動——未來從現在開始”為主題,邀請了中國相關部門的領導、整車與零部件企業集團的高層、配套設施企業集團的高層、管理企業的相關負責人、新能源企業產業園區的領導,以及權威的專家和學者、金融行業的高管等齊聚一堂,就節能與新能源汽車產業發展的戰略目標與方向、汽車企業的節能與新能源戰略與行動、節能與新能源汽車發展規劃及示範城市情況等內容進行了展開式的談論。原國家發改委副主任、國家能源局局長張國寶、國務院發展研究中心產業經濟研究部主任王曉明、中國城市電動汽車創新聯盟副會長陳勁松,整車與零部件企業集團的高層如北京汽車集團、富豪公司、鄭州宇通客車股份有限公司、比亞迪、安凱汽車股份有限公司等嘉賓領導圍繞著本屆論壇的主題進行深入細緻的討論,深入剖析節能與新能源汽車發展規劃示範城市和運營推廣,共同探討以創新的模式協調各方的力量,為新能源汽車的市場推廣形成有效的主推力。

10月18日,同期舉辦了中國國際純電動車、混合動力和燃料電池車及關鍵零部件技術交流研討會。中國汽車技術研究中心電池首席專家王芳博士、中國化學與物理電源行業協會秘書長劉彥龍、中信國安盟固利新能源科技有限公司研究院副院長劉正耀、國網北京市電力公司電力科學研究院電源技術中心主任遲忠君、中國科學院電工研究所研究員、中國電工技術學會電動車輛專委會主任委員溫旭輝、美國(EDI)易迪艾技術長 Andy Frank、北斗經濟技術產業創新聯盟執行副主席張東普和各權威的專家、學者北京交通大學電氣工程學院院長姜久春、哈爾濱工業大學電氣工程院副院長朱春波、清華大學張俊智教授等就動力電池、儲能設備,充電及服務、電機及驅動系統、系統控制與資訊系統及互聯網運用等專題進行深入的技術交流研討。

同期活動

【日期:2015年10月21日】
1、時間:上午9:30-10:00
項目:開館儀式
內容:2015節能與新能源汽車成果展開館儀式
地點:國家會議中心(序廳)

2、時間:全天
項目:2015新能源汽車產業技術創新工程技術交流會
主辦單位:創新工程辦公室、車展組委會;
內容:組織召開創新工程項目技術交流會,作為創新工程項目單位間技術交流、經驗分享的平臺。屆時,工信部、科技部、財政部及發展改革委相關領導將蒞臨指導
地點:國家會議中心

3、時間:上午11:00-11:30
項目:2015車內空氣品質評價工作新聞發佈會,
主辦單位:中國品質認證中心等;
內容:現場發佈2015年車度內空氣品質評價工作結果,向媒體和公眾發佈相應資料和檢測報告,研究、部署下一步車內空氣品質評價工作。
地點:國家會議中心

4、時間:下午
項目:2015車內空氣品質評價工作研討會
主辦單位:中國品質認證中心等;
內容:現場發佈2015年車度內空氣品質評價工作結果,向媒體和公眾發佈相應資料和檢測報告,研究、部署下一步車內空氣品質評價工作。
地點:國家會議中心

5、時間:下午
項目:2015中國互聯網+新能源汽車高峰論壇暨中國國際純電動車、混合動力車和燃料電池車及關鍵零部件技術交流研討會
主辦單位:中國電工技術學會、中國貿促會機械行業分會等;
內容:政產學研用、產業上下游、國內外行業專家齊聚一堂,為中國節能與新能源汽車發展建言獻策;媒體集中採訪
地點:國家會議中心

6、時間:下午
項目:2016年客車藍皮書啟動儀式及新能源商用車研討會
主辦單位:方得網、車展組委會;
內容:研究探討新能源技術在商用車領域的應用情況及未來發展趨勢。
地點:國家會議中心

【日期:2015年10月22日】
1、時間:上午
項目:節能與新能源汽車發展成果彙報會
主辦單位:工業和資訊化部、財政部、科技部、發展改革委;
內容:主會場(全體大會)
地點:國家會議中心一層大宴會廳B+C

2、時間:下午
項目:節能與新能源汽車技術研討會(分會場)
主辦單位:中國汽車工業協會
地點:國家會議中心一層多功能A

3、時間:下午
項目:新能源汽車專利成果研討會(分會場)
主辦單位:中國汽車工程研究院股份有限公司;
地點:國家會議中心三層301A+B

4、時間:下午
項目:新能源汽車示範城市經驗交流會(分會場)
主辦單位:中國汽車工程學會;
地點:國家會議中心三層302A+B

5、時間:下午
項目:電動汽車國際標準研討會
主辦單位:中國汽車技術研究中心;
內容:分會場
地點:國家會議中心三層303A+B

【日期:2015年10月23日】
時間:上午
項目:節能與新能源汽車政府採購資訊洽談會
主辦單位:政府採購資訊報、車展組委會;
內容:國內新能源汽車的推廣主要是通過政府採購等途徑進行,隨著國家節能減排各項政策的落實,新能源汽車的政府採購市場的推廣還有更大空間。
地點:國家會議中心

【日期:2015年10月21-24日】
1、時間:全天
項目:“尊享體驗”試乘試駕活動
內容:設立VIP嘉賓試乘試駕專場;專業觀眾和示範城市代表專場;普通觀眾、市民專場;國外使館官員、海外留學生、外賓及展會觀眾專場;媒體試駕專場;駕校師生、陪練和高校師生專場;團購和車友會專場;高爾夫球友會和金融機構專場等多個場次,讓更多人接觸使用節能與新能源車,通過體驗切身感受節能與新能源汽車的性能和優勢,向社會各界普及推廣節能環保出行理念。
地點:國家會議中心外場、老北京停車場等

2、時間:全天
項目:2015全國高校汽車科技文化節
內容:開幕儀式;高校節能環保知識大賽複賽、決賽、頒獎儀式;高校汽車設計邀請賽頒獎儀式;高校節能環保汽車形象大使聘用儀式;高校師生現場體驗活動等。
地點:國家會議中心

3、時間:全天
項目:車內環保安全知識趣味講堂及抽獎活動
內容:現場開展車內環保安全知識介紹,穿插互動環節答題,每場隨機抽獎。
地點:國家會議中心

4、時間:全天
項目:尋新集贊,尋找新能源汽車有獎
內容:活動現場諮詢台領取表格《尋“新”集贊》,到場館內所有整車展區尋找新能源汽車,到各個展臺印章“贊”,領取精美禮品。
地點:國家會議中心E1-E4館

5、時間:全天
項目:老人•孩子•藍天攝影大賽
內容:由觀眾自行尋找自己喜歡的節能環保和新能源汽車與其合影留念,拍照者可上傳到指定官網曬幸福,參與現場抽獎;現場體驗、風采展示。
地點:國家會議中心E1-E4館

6、時間:全天
項目:Blue Auto 惠享金秋購車節
內容:組織優惠購車活動,通過特價車超市,趣味試駕體驗,看車抽車等環節,讓消費者體驗不一樣的駕乘和購車感受,積累潛在客戶,促進新車成交。
地點:國家會議中心

7、時間:全天
項目:我的地盤 你做主—2015觀眾票選人氣節能與新能源車大獎
內容:由觀眾和線上(微信、微博、官網等)投票,選出最具人氣、最受歡迎、最具影響力的節能車型/純電動車型/混合動力車型/插電式混合動力車型等獎項
地點:國家會議中心

【日期:2015年10月24日】
時間:上午
項目:新心有約—車友沙龍活動
內容:車友聚會;參觀新能源汽車;參與現場活動;專車試乘試駕體驗。
地點:國家會議中心

【展會諮詢】
北京盛大超越國際展覽有限公司
連絡人:岳巍                               
手  機:+86 135 5286 5285                           
電  話: +86 10 6329 0215                   
傳  真: +86 10 5141 8155
E-mail:                  

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

【其他文章推薦】

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

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

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

電腦屏幕太小不夠用?這有妙招!

前段時間跟大家探討了 Vim 的顏色方案的話題,取得了不錯的反響,大家可以點擊以下鏈接回顧那篇文章:

今天我們來介紹 Vim 的分屏功能

為什麼需要分屏功能?其實需求場合有很多。比如,我現在屏幕很大,但我們的代碼一般是左對齊,右邊很空,這樣我們就可以通過分屏來充分利用右邊的屏幕。再如,我現在想同時查看多個文檔,除了打開多個終端外,我們還可以通過分屏來達到我們的目的。

當然類似的場合還有很多,只要我們充分挖掘,肯定能挖掘出更多需求。

下面我們就來詳細介紹 Vim 的分屏操作。

分屏功能基本操作

首先我們隨便打開一個代碼文件。為了方便演示,代碼長度越長越好。

vim test.c

現在讓我們將界面分成左右兩部分。首先我們按 ctrl+w ,緊接着按 v 。這樣操作之後,屏幕就一分為二了,如下圖示:

如果我們想要三等分怎麼操作?很簡單,在上面的基礎之上,我們再次執行一遍上面的操作,屏幕就三等分了。

如果你覺得這樣操作太麻煩,我們還可以通過在末行模式執行以下命令達到同樣的效果:

:vsplit

既然是命令,那就肯定有縮寫:

:vsp

簡直簡單到離譜…來點有水平的~讓我們從頭開始,這次我們將屏幕進行橫向分割。首先我們在末行模式下運行以下命令:

:split

同樣的,我們也有縮寫命令:

:sp

這個功能也可通過鍵盤組合鍵實現。對於水平分割來講,先按 ctrl + w ,然後按 s 。同樣,所有這些字母都是小寫的,並且是英文輸入狀態。

分屏之間切換

如前文所言,分屏在很多場景下非常有用,但是,你如果不能從一個分屏切換到另一個分屏,那這種拆分視圖就完全沒意義了。下面讓我給大家展示如何從一個分屏切換另一個分屏。

在這裏,我設置了同一文件的4個分割視圖。

剛開始時,光標位於第一個分屏。現在,假如我們要切換到右側分屏,我們需要先按 ctrl + w ,然後按 l

同樣地,假如我們要切換到左側窗口,需要先按 ctrl + w ,然後按 h

如果你是進行橫向分割屏幕的,那麼就只能上下移動光標。要切換到上面的分屏,需要先按 ctrl + w ,然後按 k

同樣地,如果想要切換到下面的分屏,那麼就需要先按 ctrl + w,然後按 j

在不同分屏下進行編輯

到此為止,我相信大家已經學會了怎麼進行分屏操作了。那分完屏,我們怎麼在這些分屏下進行文本編輯呢?其實,每個分屏都可以視為一個完整的 Vim 窗口,我們平常怎麼編輯的,就怎樣去編輯文檔。

複製及粘貼操作

與未分屏是基本無差的,大家可以在從一個分屏里複製一段文本,再切換到另一個分屏進行粘貼。操作都是一樣的,只是需要在不同分屏里切換而已。

改變分屏尺寸

默認情況下,Vim 是按等分進行分屏操作的。如果我們想最大化/最小化某個分屏,要如何操作?

如果要將當前窗口加寬到最大尺寸,需要先按 ctrl + w ,然後按 |(注意:不是小寫 L ,是與或非的那個與 | )。

如果你想把當前窗口高度加高到最大尺寸,那麼需要使用 ctrl + w ,然後使用 **_** 。

那如果想要重置所有分割窗口的大小,那麼使用 ctrl+w ,然後按 =

默認情況下,Vim 在進行分屏操作時,每個分屏是等寬或等高的。如果要自定義分屏的寬度,大家可以使用以下結構:

:<width> vsp

同樣地,對於水平分割而言,可以使用類似結構自定義高度:

:<height> sp

在同一個 Vim 窗口下打開多個不同文件

目前為止,所有的 Vim 分屏都是同一個文件的副本,但在很多情況下,我們需要打開多個不同的文件。我們可以將 Vim 窗口進行分屏,再在不同的分屏里打開不同的文件。

如何在 Vim 中打開一個新文件?我們可以使用以下命令:

:e<path_to_file>/filename.extension

例如,我們想要在一個全新的 Vim 實例中打開 vimrc,我們可以使用以下命令:

:e~/.vimrc

打開 vimrc 之後,我們想要將屏幕水平切分並打開一個新文件,可以使用以下命令:

:sp<file_path>

而對於垂直分割屏幕,使用的也是類似的結構:

:vsp<file_path>

小結

Vim 分屏講到這裏就要跟大家告一段落了。這種操作還是比較有趣,並且十分實用。

本文介紹了 Vim 分屏的一些基本操作及編輯方法,合理利用這個功能可以使我們更加高效使用電腦屏幕,提高我們的效率。
—————–

我是良許,世界500強外企 Linux 開發工程師,專業生產 Linux 乾貨。歡迎關注我的公眾號「良許Linux」,裏面分享了 Linux入門、基礎、進階 等系列教程,同時也有 Git、Vim、開源項目 等技術乾貨。公眾號後台回復「1024」獲取最新最全的技術資料,回復「入群」進入高手如雲技術交流群。

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

【其他文章推薦】

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

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

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

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

中國有望成全球最大電動車市場

11日,在中國汽車工業協會新聞發佈會上,中汽協常務副會長兼秘書長董揚指出,新能源汽車正從示範向增長階段發展,逐漸成為中國汽車行業的重要組成部分,中國有望成為全球最大的電動汽車市場。   中國汽車工業協會的最新資料顯示,1-7月,新能源汽車生產95530輛,銷售89549輛,同比分別增長2.5倍和2.6倍;7月當月,新能源汽車銷售同比增長3.3倍。   據《中國汽車消費者白皮書》分析,政策導向、續航里程、充電時間和售後保障將成為決定新能源車發展速度的主要因素。

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

【其他文章推薦】

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

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

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

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