Nacos源碼解讀11——客戶端怎么讀取最新的配置信息

項目啟動怎么讀取的配置信息

自動裝配

SpringBoot 自動裝配機制 加載 WEB/INF spring.factories
會將如下幾個Bean加載到ioc 容器中

@Bean@ConditionalOnMissingBeanpublic NacosConfigProperties nacosConfigProperties() {return new NacosConfigProperties();}@Bean@ConditionalOnMissingBeanpublic NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);}@Beanpublic NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {return new NacosPropertySourceLocator(nacosConfigManager);}/*** Compatible with bootstrap way to start.* @param beans configurationPropertiesBeans* @return configurationPropertiesRebinder*/@Bean@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)@ConditionalOnNonDefaultBehaviorpublic ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {// If using default behavior, not use SmartConfigurationPropertiesRebinder.// Minimize te possibility of making mistakes.return new SmartConfigurationPropertiesRebinder(beans);}

在SpringBoot啟動的時候會將觸發 PropertySourceLocator的locate方法去讀取配置 而NacosPropertySourceLocator實現了PropertySourceLocator接口所以會觸發NacosPropertySourceLocator的locate方法

	@Overridepublic PropertySource<?> locate(Environment env) {//設置 Environment(上下文環境)nacosConfigProperties.setEnvironment(env);//獲取遠程調用服務 ConfigServiceConfigService configService = nacosConfigManager.getConfigService();if (null == configService) {log.warn("no instance of config service found, can't load config from nacos");return null;}//超時時間 3000long timeout = nacosConfigProperties.getTimeout();nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,timeout);String name = nacosConfigProperties.getName();//應用名String dataIdPrefix = nacosConfigProperties.getPrefix();if (StringUtils.isEmpty(dataIdPrefix)) {dataIdPrefix = name;}if (StringUtils.isEmpty(dataIdPrefix)) {dataIdPrefix = env.getProperty("spring.application.name");}CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);//共享配置 shared-configsloadSharedConfiguration(composite);//擴展配置 extension-configsloadExtConfiguration(composite);//本地配置  dataIdloadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);return composite;}

共享配置加載

	private void loadSharedConfiguration(CompositePropertySource compositePropertySource) {List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties.getSharedConfigs();//如果配置了共享配置		if (!CollectionUtils.isEmpty(sharedConfigs)) {//檢查共享配置checkConfiguration(sharedConfigs, "shared-configs");//加載配置信息loadNacosConfiguration(compositePropertySource, sharedConfigs);}}

擴展配置加載

	private void loadExtConfiguration(CompositePropertySource compositePropertySource) {List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties.getExtensionConfigs();//如果配置了擴展配置	if (!CollectionUtils.isEmpty(extConfigs)) {//擴展配置檢查checkConfiguration(extConfigs, "extension-configs");//加載配置信息loadNacosConfiguration(compositePropertySource, extConfigs);}}
	private void loadNacosConfiguration(final CompositePropertySource composite,List<NacosConfigProperties.Config> configs) {//遍歷配置信息		for (NacosConfigProperties.Config config : configs) {//獲取配置內容的數據格式  比如yaml 之類的String fileExtension = config.getFileExtension();//如果是空if (StringUtils.isEmpty(fileExtension)) {//從DataId里面獲取fileExtension = NacosDataParserHandler.getInstance().getFileExtension(config.getDataId());}//加載配置信息loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(),fileExtension, config.isRefresh());}}

本地配置加載

	private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix,NacosConfigProperties properties, Environment environment) {//文件擴展名 .ymal/properties    (spring.cloud.nacos.config.file-extension)String fileExtension = properties.getFileExtension();//GroupString nacosGroup = properties.getGroup();// load directly once by default//加載 配置文件名的文件loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,fileExtension, true);// load with suffix, which have a higher priority than the default//加載 配置文件名.yml/Property的文件loadNacosDataIfPresent(compositePropertySource,dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);// Loaded with profile, which have a higher priority than the suffix//根據 .dev/.prd 進行逐項加載for (String profile : environment.getActiveProfiles()) {String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,fileExtension, true);}}

加載配置信息

	private void loadNacosDataIfPresent(final CompositePropertySource composite,final String dataId, final String group, String fileExtension,boolean isRefreshable) {if (null == dataId || dataId.trim().length() < 1) {return;}if (null == group || group.trim().length() < 1) {return;}//獲取屬性源     NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,fileExtension, isRefreshable);this.addFirstPropertySource(composite, propertySource, false);}private NacosPropertySource loadNacosPropertySource(final String dataId,final String group, String fileExtension, boolean isRefreshable) {//是否配置了刷新 每次判斷會觸發一次回調if (NacosContextRefresher.getRefreshCount() != 0) {//不支持動態刷新 從本地緩存拿if (!isRefreshable) {return NacosPropertySourceRepository.getNacosPropertySource(dataId,group);}}//遠程拿數據return nacosPropertySourceBuilder.build(dataId, group, fileExtension,isRefreshable);}

本地獲取數據

當不支持動態刷新的時候直接從本地緩存拿數據

	public static NacosPropertySource getNacosPropertySource(String dataId,String group) {return NACOS_PROPERTY_SOURCE_REPOSITORY.get(getMapKey(dataId, group));}

從服務端獲取數據

	NacosPropertySource build(String dataId, String group, String fileExtension,boolean isRefreshable) {//加載服務端配置Map<String, Object> p = loadNacosData(dataId, group, fileExtension);//構建配置信息NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,p, new Date(), isRefreshable);//保存本地緩存NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);return nacosPropertySource;}
	private List<PropertySource<?>> loadNacosData(String dataId, String group,String fileExtension) {String data = null;.....//獲取服務信息data = configService.getConfig(dataId, group, timeout);.....		return Collections.emptyList();}
 @Overridepublic String getConfig(String dataId, String group, long timeoutMs) throws NacosException {return getConfigInner(namespace, dataId, group, timeoutMs);}
    private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {group = null2defaultGroup(group);ParamUtils.checkKeyParam(dataId, group);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setTenant(tenant);cr.setGroup(group);// 1.優先使用本地緩存配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) { LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));cr.setContent(content);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;}try {//2.委派給worker從遠程服務器獲取String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);cr.setContent(ct[0]);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;} catch (NacosException ioe) {if (NacosException.NO_RIGHT == ioe.getErrCode()) {throw ioe;}LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",agent.getName(), dataId, group, tenant, ioe.toString());}LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));// 本地快照目錄拿content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);cr.setContent(content);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;}

本地緩存文件讀取

    public static String getFailover(String serverName, String dataId, String group, String tenant) {//從本地文件中獲取File localPath = getFailoverFile(serverName, dataId, group, tenant);if (!localPath.exists() || !localPath.isFile()) {return null;}try {//讀文件return readFile(localPath);} catch (IOException ioe) {LOGGER.error("[" + serverName + "] get failover error, " + localPath, ioe);return null;}}

從Nacos服務端獲取數據

    public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout, boolean notify)throws NacosException {//分組等于空設置默認分組 if (StringUtils.isBlank(group)) {group = Constants.DEFAULT_GROUP;}//配置信息讀取return this.agent.queryConfig(dataId, group, tenant, readTimeout, notify);}
@Override
public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeouts, boolean notify)throws NacosException {ConfigQueryRequest request = ConfigQueryRequest.build(dataId, group, tenant);request.putHeader(NOTIFY_HEADER, String.valueOf(notify));RpcClient rpcClient = getOneRunningClient();if (notify) {CacheData cacheData = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));if (cacheData != null) {rpcClient = ensureRpcClient(String.valueOf(cacheData.getTaskId()));}}// 發送一個grpc請求讀取配置信息ConfigQueryResponse response = (ConfigQueryResponse) requestProxy(rpcClient, request, readTimeouts);ConfigResponse configResponse = new ConfigResponse();......
}

服務端讀取配置信息

ConfigQueryRequestHandler

@Override
@TpsControl(pointName = "ConfigQuery", parsers = {ConfigQueryGroupKeyParser.class, ConfigQueryGroupParser.class})
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public ConfigQueryResponse handle(ConfigQueryRequest request, RequestMeta meta) throws NacosException {try {return getContext(request, meta, request.isNotify());} catch (Exception e) {return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage());}}

這里太長懶得貼 反正就是會先用groupKey=dataid+group+namespace 服務端對每一個配置文件都有服務端的內存緩存,這里使用讀寫鎖獲取鎖,為了避免并發修改的情況
然后會判斷當前模式是不是單機模式 并且不使用mysql 做數據存儲的則從內置的數據庫拿數據 如果配的是mysql 則從mysql中拿數據返回

緩存配置信息到本地

	public static void collectNacosPropertySource(NacosPropertySource nacosPropertySource) {NACOS_PROPERTY_SOURCE_REPOSITORY.putIfAbsent(getMapKey(nacosPropertySource.getDataId(),nacosPropertySource.getGroup()), nacosPropertySource);}

配置信息動態刷新

springboot自動注入的時候會去注入一個 NacosConfigAutoConfiguration 然后再這里面會注入一個 NacosContextRefresher進行數據的刷新 當spring啟動的時候 會發送一個ApplicationReadyEvent事件而NacosContextRefresher會監聽這個事件

	@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// many Spring contextif (this.ready.compareAndSet(false, true)) {this.registerNacosListenersForApplications();}}
	private void registerNacosListenersForApplications() {//是否開啟了刷新  配置文件中 spring.cloud.nacos.config.refresh-enabled = true/false if (isRefreshEnabled()) {//開啟了遍歷 外部化配置的屬性元for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) { //判斷這個屬性元是否開啟了 Refreshableif (!propertySource.isRefreshable()) {continue;}String dataId = propertySource.getDataId();//注冊監聽事件registerNacosListener(propertySource.getGroup(), dataId);}}}

注冊監聽事件

當配置發生變更的時候會回調到這個監聽事件中去發送一個刷新事件
具體怎么判斷數據變更看上一張的safeNotifyListener方法 這個方法中會有一個innerReceive方法的調用最終會到這里

	private void registerNacosListener(final String groupKey, final String dataKey) {String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);Listener listener = listenerMap.computeIfAbsent(key,lst -> new AbstractSharedListener() {//當NacosContextRefresher.getRefreshCount() != 0 判斷配置發生變化發生回調@Overridepublic void innerReceive(String dataId, String group,String configInfo) {refreshCountIncrement();//添加刷新歷史記錄nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);// todo feature: support single refresh for listening//發布事件applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));if (log.isDebugEnabled()) {log.debug(String.format("Refresh Nacos config group=%s,dataId=%s,configInfo=%s",group, dataId, configInfo));}}});try {configService.addListener(dataKey, groupKey, listener);}catch (NacosException e) {log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,groupKey), e);}}

監聽刷新事件

RefreshEventListener會監聽到刷新事件進行處理

	@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationReadyEvent) {handle((ApplicationReadyEvent) event);}else if (event instanceof RefreshEvent) {handle((RefreshEvent) event);}}
	public void handle(RefreshEvent event) {//是否就緒if (this.ready.get()) { // don't handle events before app is readylog.debug("Event received " + event.getEventDesc());Set<String> keys = this.refresh.refresh();log.info("Refresh keys changed: " + keys);}}
	public synchronized Set<String> refresh() {Set<String> keys = refreshEnvironment();//重新刷新beanthis.scope.refreshAll();return keys;}
	public synchronized Set<String> refreshEnvironment() {Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());addConfigFilesToEnvironment();//得到發生變更的keySet<String> keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();//發布EnvironmentChange事件this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));return keys;}

Environment變更事件監聽

ConfigurationPropertiesRebinder 監聽到事件重新進行數據的綁定

	@Overridepublic void onApplicationEvent(EnvironmentChangeEvent event) {if (this.applicationContext.equals(event.getSource())// Backwards compatible|| event.getKeys().equals(event.getSource())) {//數據綁定rebind();}}
	@ManagedOperationpublic void rebind() {this.errors.clear();for (String name : this.beans.getBeanNames()) {rebind(name);}}
	@ManagedOperationpublic boolean rebind(String name) {if (!this.beans.getBeanNames().contains(name)) {return false;}if (this.applicationContext != null) {try {Object bean = this.applicationContext.getBean(name);if (AopUtils.isAopProxy(bean)) {bean = ProxyUtils.getTargetObject(bean);}if (bean != null) {// TODO: determine a more general approach to fix this.// see https://github.com/spring-cloud/spring-cloud-commons/issues/571if (getNeverRefreshable().contains(bean.getClass().getName())) {return false; // ignore}//先卸載this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);//重新初始化this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);return true;}}catch (RuntimeException e) {this.errors.put(name, e);throw e;}catch (Exception e) {this.errors.put(name, e);throw new IllegalStateException("Cannot rebind to " + name, e);}}return false;}

總結

springboot啟動的時候怎么加載的nacos中的配置

1.springboot啟動的時候會通過自動注入 將NacosPropertySourceLocator注入到bean容器中 然后會調用到locate方法中進行配置的讀取
2.配置讀取會先判斷是否配置了自動刷新 如果沒有配置則直接從緩存中讀取
3.如果配置了自動刷新 會先從本地快照中讀取 如果讀取到了就返回并加入到本地緩存中
4.如果快照中也沒有讀取到 則通過grpc請求從服務端讀取 從服務端讀取的時候會先生成一個讀寫鎖防止有問題 他會判斷是集群還是單機啟動 如果是單機啟動并且沒有使用mysql 則從內嵌的數據庫里讀取 如果是集群并且配置了mysql則從mysql 讀取返回給客戶端
5.如果從服務端也沒讀取到則從本地磁盤讀取

怎么實現動態刷新配置

1.springboot啟動的時候會通過自動注入 會注入一個NacosContextRefresher到bean容器中 這里面會注冊一個監聽器用來監聽事件變更的一個回調
2.當服務端有配置變更之后會推送給客戶端進行配置的修改并觸發這個回調
3.然后回調中會觸發一個刷新事件
4.當準備完畢之后會去發送一個spring中配置修改的一個事件
5.這個事件中會觸發bean的重新綁定事件 實際上就是將老的bean給卸載將新的bean重新加載來實現配置的實時變更

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212462.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212462.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212462.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【算法Hot100系列】兩數之和

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…

【rabbitMQ】模擬work queue,實現單個隊列綁定多個消費者

上一篇&#xff1a; springboot整合rabbitMQ模擬簡單收發消息 https://blog.csdn.net/m0_67930426/article/details/134904766?spm1001.2014.3001.5502 在這篇文章的基礎上進行操作 基本思路&#xff1a; 1.在rabbitMQ控制臺創建一個新的隊列 2.在publisher服務中定義一個…

MySQL中的數據類型

MySQL中的數據類型 大家好&#xff0c;我是微賺淘客系統的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討MySQL中的數據類型&#xff0c;這是數據庫設計中至關重要的一部分。數據庫作為程序的底層支持&#xff0c;數據類型的選擇…

[python]利用whl輪子文件python3.12安裝talib

ta-lib目前很多人使用&#xff0c;網上也有很多人下載whl文件直接pip安裝即可&#xff0c;但是最新版本3.12沒有出來&#xff0c;因此本人獨家制作python 3.12版本whl文件&#xff0c;從源碼開始編譯生成。TA-Lib-0.4.28-cp312-cp312-win-amd64.whl &#xff0c;注意這個whl文件…

Java 多線程下的單例模式

單例對象&#xff08;Singleton&#xff09;是一種常用的設計模式。在Java應用中&#xff0c;單例對象能保證在一個JVM中&#xff0c;該對象只有一個實例存在。正是由于這個特 點&#xff0c;單例對象通常作為程序中的存放配置信息的載體&#xff0c;因為它能保證其他對象讀到一…

JWT的原理

在談及jwt原理前,我們其實對jwt并不陌生,對于有經驗的碼農,大都聽過或者實踐過,對于一些初學者,凡是談及安全方面的問題,總是覺得很復雜,感覺不是自己能搞得懂得,但其實無非也是加密解密的過程,不要想的太復雜,我們先說一說JWT在生產上的應用 JWT在生產上的應用 傳遞用戶身份信…

Android系統中使用Cunit測試C/C++接口

Android系統中使用Cunit測試C/C接口 Cunit是C/C語言的單元測試框架&#xff0c;但常用于Windows和Linux開發中。 Android系統中經常有jni、so庫、hal service等都是C/C實現&#xff0c;本文講解如何將Cunit嵌入Android中&#xff0c;用于測試一些C/C api。 Cunit簡介 Cunit是很…

全面解析“由于找不到hid.dll,無法繼續執行代碼”的4個解決方法

在計算機使用過程中&#xff0c;我們經常會遇到一些錯誤提示&#xff0c;其中之一就是“找不到hid.dll”。這個問題通常出現在嘗試運行某個程序或訪問某個設備時。那么&#xff0c;當我們遇到這個問題時&#xff0c;應該如何解決呢&#xff1f;本文將詳細介紹找不到hid.dll的解…

高校需要哪些大數據實訓平臺?

當前&#xff0c;數據已成為重要的生產要素&#xff0c;大數據產業作為以數據生成、采集、存儲、加工、分析、服務為主的戰略性新興產業&#xff0c;是激活數據要素潛能的關鍵支撐&#xff0c;是加快經濟社會發展質量變革、效率變革、動力變革的重要引擎。 泰迪大數據實驗…

Angular 14帶來了類型化表單和獨立組件

獨立組件通過減少對ngmodule的需求&#xff0c;有望簡化Angular應用的開發。 介紹 Angular 14是谷歌開發的、基于typescript的web應用框架的最新版本&#xff0c;它以輸入表單和獨立組件的開發者預覽版為特色。 其特性包括&#xff1a; 一個基于組件的框架&#xff0c;用于構…

Fortran讀取netcdf文件/WRF中的文件讀取

一直很好奇WRF到底如何通過netcdf庫讀取netcdf文件&#xff0c;正巧有個機會&#xff0c;試了下fortran讀取nc文件&#xff0c;總結一下。 netcdf庫 Fortran讀取nc文件需要依賴netcdf外部庫。安裝該庫以后&#xff0c;會有專門寫給ffortran函數聲明的頭文件&#xff1a;netcd…

數據類型·

定義 數據類型是指在編程語言中&#xff0c;能夠表示不同種類的數據值并對其進行操作的集合。在不同的編程語言中&#xff0c;數據類型可能有所不同&#xff0c;但通常包括基本數據類型和復合數據類型兩種。 基本數據類型通常包括整數、浮點數、布爾值、字符等。這些類型的數…

231210 刷題日報

單調棧&#xff1a; 為啥需要單調棧&#xff1f;因為棧的后入先出特性方便從棧頂刪除剛入棧的元素 496. 下一個更大元素 I 739. 每日溫度 單調對列&#xff1a; 為啥要用單調對列&#xff1f;因為像滑動窗口這種題目&#xff0c;窗口兩端都需要插入和刪除&#xff0c;所以需…

Python滿屏飄字表白代碼

? 目錄 系列文章 寫在前面 Turtle入門 滿屏飄字 寫在后面 系列文章 序號文章目錄直達鏈接表白系列1浪漫520表白代碼https://want595.blog.csdn.net/article/details/1306668812滿屏表白代碼https://want595.blog.csdn.net/article/details/1297945183跳動的愛心https://…

CF1898B Milena and Admirer(貪心)

題目鏈接 題目大意 有一個長度為 n 的數組 做操作使這個數組不遞減&#xff1a; 把一個數分成兩個數&#xff0c;例如&#xff1a;x 分為 a 和 b&#xff0c; x a b 求最小操作次數 思路 見注釋 代碼 #include<bits/stdc.h> #define int long long using names…

Shutter的安裝及使用

概要&#xff1a;本篇主要講述截圖軟件Shutter的安裝和使用&#xff0c;操作系統是Ubuntu22.04 一、安裝 sudo apt install shutter 二、區域截圖 1、打開Shutter&#xff0c;點擊Selection 2、提示信息 3、框選矩形區域 按住鼠標左鍵&#xff0c;拖動鼠標&#xff0c;松…

IT行業最被低估的六項技術,再加上一項尚未消亡的技術

2023年&#xff0c;生成式人工智能——更具體地說是ChatGPT——吸引了業界的廣泛關注&#xff0c;深得董事會、首席執行官和其他高管的一致贊賞&#xff08;也不乏害怕情緒&#xff09;。當然&#xff0c;他們的熱情是有道理的&#xff0c;多項研究發現&#xff0c;人工智能正在…

Electron[4] Electron最簡單的打包實踐

1 背景 前面三篇已經完成通過Electron搭建的最簡單的HelloWorld應用了&#xff0c;雖然這個應用還沒添加任何實質的功能&#xff0c;但是用來作為打包的案例&#xff0c;足矣。下面再分享下通過Electron-forge來將應用打包成安裝包。 2 依賴 在Electron[2] Electron使用準備…

[山東大學操作系統課程設計]實驗四+實驗五

0.寫在前面&#xff1a; 為什么這次把兩個實驗放在一起寫了&#xff0c;因為實驗五的要求就是在實驗四的基礎上完成實現的。但是我得實現說明&#xff0c;我的實驗四雖然完成了要求&#xff0c;但是無法在我自己的實驗四的基礎上完成實驗五&#xff0c;這是一個很大的問題&…

軟考考前背過-軟件設計師

今年5月份開始準備考&#xff0c;沒想到會突然改革&#xff0c;還好刷題刷的多&#xff0c;也過了。 跟著B站up主的視頻學的&#xff0c;都學了一遍之后才開始刷題&#xff0c;平時要上班&#xff0c;也就下班和周末能學&#xff0c;時間可能拉的比較長&#xff0c;學完前面的內…