2022年5月16日 星期一

自動幫用戶開啟RichMenu與鍵盤or語音輸入

LINE在2022/5/13增加了postbackAction的屬性,讓發人員可以藉由送出一個含有postbackAction的訊息(類似底下這樣),來幫用戶來開啟(或關閉)rich menu,甚至可以開啟輸入鍵盤和語音:
enter image description here

上圖 A 的部分,是一個 Buttons Template Message,其中有四個按鈕,其類型都是我們先前介紹過的 postbackAction。

在過去,當用戶點下postbackAction按鈕時,只會讓伺服器端的WebHook收到一則訊息,但LINE 2022/5/13 增加了此功能之後,postbackAction多了兩個重要的屬性,分別是inputOption與fillInText。

inputOption可以是底下的值:

  • closeRichMenu: 關閉 rich menu
  • openRichMenu: 開啟 rich menu
  • openKeyboard: 開啟輸入鍵盤
  • openVoice: 開啟語音輸入

而 fillInText 則是當inputOption為openKeyboard時,開啟的文字輸入鍵盤中的預設文字。

例如,當建立 postbackAction 的程式碼如下:

Actions.Add(new  isRock.LineBot.PostbackAction() 
{
label  =  "開鍵盤",
data  =  "openKeyboard",
displayText  =  "開鍵盤",
fillInText  =  "預設文字",
inputOption  =  "openKeyboard"
});

則用戶點選該選單後,結果如下:
enter image description here

當您建立 postbackAction 的程式碼如下:

Actions.Add(new  isRock.LineBot.PostbackAction() 
{
label  =  "開選單",
data  =  "openRichMenu",
displayText  =  "開選單",
inputOption  =  "openRichMenu"
});

則用戶點選該PostbackAction選項,結果如下:
enter image description here

你還可以透過底下指令,來開啟語音輸入:

Actions.Add(new  isRock.LineBot.PostbackAction() 
{
label  =  "開語音輸入",
data  =  "openVoice",
displayText  =  "開語音輸入",
inputOption  =  "openVoice"
});

執行結果如下:
enter image description here

很有趣吧,這功能可以讓你的LINE Bot與用戶的互動更加的便捷, .net core 的範例程式碼在底下:
https://github.com/isdaviddong/ExLineRichMenuautomaticOpeningAndClosing

你只需要將 LineBotSDK 升級到 2.5.33-beta 以上的版本即可。

2022年5月13日 星期五

Azure DevOps 中的 Release Gate

enter image description here
在使用自動化上版的過程當中,你大概或多或少會想要使用簽核(Approval)的功能。簽核功能讓你可以自動佈署到特定站台(例如正式機)之前,形成一個 “把關” 的功能。

雖然感覺用起來很酷,但坦白說,我們對於上新版程式前的Approval行為,在態度上是不鼓勵的。因為多年下來,並沒有案例顯示,上版前的簽核真能夠減少什麼風險或是降低問題發生的機率。反倒是常常因為Approver卡住流程,造成自動化效率的降低。

如果企業是為了要有人負責,才配置這個Approver,那我們就更不鼓勵了。因為大部分的Approver其實無法在上版前真的做什麼檢查,常常有簽核權限的PM只是回頭問問工程師 : 『可以上正式機了嗎? 可以? 那我就按"同意"了唷』。這樣的橡皮圖章其實讓人很心酸。

事實上,大部分能做該做的檢查,根本早就在(也應該要在)自動化流程中被完成,而非靠人工的檢核。

更好用的機制 - Release Gate

比起Approvals的設定,Release Gate其實是配合自動化部署更好的選擇。

設定的方式類似 Approvals,只需要先將選項設為 Enabled即可啟動(下圖A),接下來就是設定要作為Gate的機制(下圖B):
enter image description here

按下Add之後,你可以選擇要作為Gate的機制:

enter image description here

系統已經內建不少的Gate類型可供選擇,其中比較常用的大概是『Invoke REST API』和『Query work items』。另外『SonarCloud Quality Gate』則是外掛的套件,配合SonarCloud軟體品質掃描用的。

當你設定了Release Gate這個機制,它將會每隔幾分鐘(最少五分鐘)檢查一次你設定的條件,當該條件成立的時候,才會允許Pipeline繼續往下進行。

這個功能可以幫助我們,在自動化的過程中減少不需要的人力介入或簽核,即可自動檢查特定條件是否成立。你也可以撰寫Azure Function,或是使用Invoke REST API方法來呼叫特定API,取得回傳值,以判斷是否可以讓Release Pipeline繼續往下運行。

在高強度(一天數次或一周數次)的頻繁交付過程中,Release Gate比起人工簽核(Approval)更是合作上版前的自動化關卡檢查機制。

來,以後試著放棄橡皮圖章吧。


參考資料:
Azure DevOps 顧問實戰
https://www.tenlong.com.tw/products/9786263241251?list_name=b-r7-zh_tw

2022年4月27日 星期三

Azure DevOps in Action - 建立Linux環境的Build Agent

Azure DevOps也可以輕易地建立在Linux環境上的Build Agent,底下我們將會採用Ubuntu的VM環境來示範這個動作。

首先,我們建議您用Azure上的Ubuntu 20.04虛擬機範本,相關的建立參數如下:
enter image description here
我採用D4s_v3的虛擬機等級,在East Asia資料中心建立該伺服器。同時為了讓我能夠從Windows環境連上該伺服器做後續設定,我選擇了開啟SSH(22) Port。

請牢記你建立時所輸入的帳號密碼。

在虛擬機建立完之後,可以透過PowerShell(我是使用 Windows Terminal)以ssh指令來連上該伺服器,並且輸入密碼:

ssh 帳號@IP

例如:
enter image description here

遠端登入成功之後,即可對該伺服器下達指令。

先整理一下我們登入後要做的事情,分別是:

  1. 安裝 .net core SDK(為了可以進行 dotnet build)
  2. 下載Azure DevOps Agent套件(壓縮檔)
  3. 解壓縮套件
  4. 安裝套件並進行設定(過程中需用到PAT)
  5. 執行Agent

整個動作,可以從Azure DevOps的Orgnization Settings開始:
enter image description here
從Orgnization Settings選單點選Agent Pools,選擇Default(即為Self-Hosted Agent),接著點選New Agent。

在出現的畫面中,請點選Linux,你會看到安裝Linux Build Agent的步驟:
enter image description here
首先,請點選上圖A的部分,複製agent套件的下載位置,在筆者截稿時,該位置為:

https://vstsagentpackage.azureedge.net/agent/2.202.1/vsts-agent-linux-x64-2.202.1.tar.gz

接著,請在powershell以ssh連線的ubuntu環境中,下達底下指令:

mkdir myagent && cd myagent

這會建立一個myagent資料夾,並且進入該資料夾中。

接著,請執行底下指令,來下載agent:

curl -O https://vstsagentpackage.azureedge.net/agent/2.202.1/vsts-agent-linux-x64-2.202.1.tar.gz

其中 curl -O 是下載檔案,而後面的url,請換成您剛才在上圖A中所複製到的最新版URL。

接著,再透過底下指令解壓縮:

tar zxvf vsts-agent-linux-x64-2.202.1.tar.gz

完成後,執行ls指令,會看到類似底下這些內容:
enter image description here
這樣我們待會就可以進行設定了。

但在此之前,我們得先準備好PAT(Personal Access Token),以及為該伺服器準備 .net core sdk環境。

參考 https://docs.microsoft.com/zh-tw/dotnet/core/install/linux-ubuntu 的說明,您可以透過運行底下指令,來安裝 .net core sdk:

wget https://packages.microsoft.com/config/ubuntu/21.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

sudo apt-get update;
sudo apt-get install -y apt-transport-https &&
sudo apt-get update &&
sudo apt-get install -y dotnet-sdk-6.0

過程中可能會需要您輸入密碼,請在powershell中執行上述指令,結果類似底下這樣:
enter image description here
若你需要安裝其它版本的.net core sdk,可以修改上面指令中的版號即可,例如:

sudo apt-get install -y dotnet-sdk-5.0

成功安裝後,我們就要進行最後一個步驟了。

請先準備好PAT,取得PAT的方式如下,請先點選Azure DevOps畫面右上角個人帳號旁邊的小人圖示:
enter image description here
接著在選單中選擇 『Personal access token』,即會出現建立PAT的畫面:
enter image description here
在出現的畫面中選擇建立新的PAT。

PAT是替代你的帳號密碼的令牌,在你指定的有限時間內,出示該令牌就等於具有你所賦予的權限。

建立好了之後,請保留該PAT,它的長像大概是底下這樣:

zs4l6xp7xwhnj7lp8ta7i4q3xhtfasaxs3vp6xxwrj6iger7i63ob2eq

接著,請回到剛才的powershell視窗,在myagent資料夾底下執行:

./config.sh

執行過程中,會需要輸入PAT:
enter image description here
請留意,上圖2中的URL,要輸入的是你的Azure DevOps站台位置,而輸入PAT之前(上圖3),會先要你按一個Enter(請仔細注意上面的英文敘述),然後才是輸入PAT(按下滑鼠右鍵即可貼上)。

後面的幾個選項都按Enter即可,完成後,你會看到Setting Saved.

接著,再執行 ./run.sh ,你會發現,系統已經開始運作,並且在等待jobs了 :
enter image description here
這時候,你就可以使用這個 agent pool來進行build job了。

你可以依照先前我們介紹過的方式,建立一個 .net core 程式的CI Build Pipeline,唯一不同的地方是,這次(下圖1)Agent Pool選擇的是Default(Private) ,其它的設定均不變:
enter image description here
當你運行該Pipeline時,會發現,這個Build Machine比Azure DevOps內建的來的快很多,自然是因為這台VM我們建立時選擇的等級是很高檔的。

沒多久,整個CI Build就順利的完成了:
enter image description here
你也會看到,PowerShell中,Ubuntu的Command Line出現相對應的提示訊息:
enter image description here
表示job被成功的完成了。

如果要停止該agent,可以在命令列按下CTRL+C,如果要移除該agent,只需要下達 ./config.sh remove指令,並輸入PAT即可:
enter image description here
建立一個私有的Linux Build Agent就是這麼簡單。

相關資源:

[書籍]Azure DevOps顧問實戰:
https://www.tenlong.com.tw/products/9786263241251?list_name=b-r7-zh_tw

[課程]敏捷開發專案管理與Azure DevOps實戰
https://www.studyhost.tw/NewCourses/ALM

2022年4月26日 星期二

No hosted parallelism has been purchased or granted.

近期上課時,許多學員在申請好Azure DevOps站台後,使用Build Pipeline時,可能會發現它發生了類似底下這樣的錯誤訊息:
enter image description here
完整的錯誤訊息是:

##[error]No hosted parallelism has been purchased or granted. To request a free parallelism grant, please fill out the following form https://aka.ms/azpipelines-parallelism-request

這段錯誤訊息的起因,是由於2021年3月之後,微軟已經取消了預設的免費pipeline使用,若您要使用免費的pipeline,必須依照上述的訊息,填寫申請表(網址如下):
https://aka.ms/azpipelines-parallelism-request

經過同學測試,如果申請成功,你會收到微軟寄來的信件,告知您已經可以使用。但因為填寫申請表曠日廢時(大概要2-3個工作天),若您不想等待,我們還有一個更簡單(但可能需要點費用)的方法。你可以用登入Azure DevOps相同的帳號,申請一個免費的Azure Trial訂閱,接著將該訂閱綁定於Azure DevOps服務即可:
enter image description here
當成功設定好訂閱(Subscription)連結之後,您必須將Paid parallel jobs設定為1(參考下圖):
enter image description here
設定完成儲存之後,立即可以使用Pipeline

特別注意,每一個 Paid parallel jobs 可能會花費 $40USD唷,請特別留意費用的產生。

2022年3月3日 星期四

使用IoC與DI有何意義? (四) 使用IoC與DI提高可測試性

enter image description here這一系列的文章,持續維持著一年更新一篇的進度,😛。

今天適逢良辰吉日,我們再次來談談,如何透過IoC與DI增加系統的可測試性。

關於單元測試(Unit Test)

我們知道,有單元測試的存在,我們得以放膽的修改程式碼,無須擔心程式碼的頻繁異動將造成意料之外的副作用。

也因此,有一些團隊追求著程式碼單元測試的覆蓋率。

然而,高覆蓋率的單元測試並不意味著就會有更高品質的程式碼,反而真正重要的是,如何為我們系統中的核心邏輯、重要的核心函式,適當的添加單元測試。

所以,我們接下來要來談一談,如何透過IoC與DI增加程式碼的可測試性。

關於可測試性

你大概也已經知道,我們可以透過Visual Studio為特定的method來建立單元測試。特別是重要的商業邏輯運算函式、或是API,都是單元測試非常能發揮功能的好對象。

然而,你可能會發現有些程式碼似乎難以測試,例如底下這個例子:

Console.Write("請輸入金額(USD):");
var amount = int.Parse(Console.ReadLine()); //100
Console.Write("請輸入人數:");
var people = int.Parse(Console.ReadLine()); //5
Financy f = new Financy();
var CostByPeople = f.SplitMoney(amount, people);
Console.Write(CostByPeople);

上面這段程式碼看起來簡單,實際上,也真的很簡單😎。

SplitMoney()是一個計算旅遊消費金額分攤的函式。

假設,你和一夥人出國旅遊,在路邊店家吃了一餐,總共100美元,你想計算每個人要負擔多少台幣,就可以使用這個函式。呼叫SplitMoney()時,傳入『美金總金額』和『人數』兩個參數,它就幫你算出一個人要付的台幣金額。

假設我們要針對這個函式進行單元測試,乍看之下似乎並不難,但我們看SplitMoney()的具體內容:

public class Financy
{
  public double SplitMoney(double USDAmount, int People)
  {
    var currencyConverter = new CurrencyConverter();
    //使用到外部函式(抓取匯率)
    double rate = currencyConverter.Convert("USD",  "TWD");
    //計算台幣總金額
    double Total = USDAmount * rate;
    //回傳一個人需要付多少錢(台幣)
    return Total / People;
  }
}

這下問題來了。

我們SplitMoney()這個方法本身不難,也不過就是把總金額除以旅遊人數,就算出平均金額了。倘若我們需要為SplitMoney()這個方法撰寫單元測試,似乎也沒啥問題。

但這邊出現了個障礙,你知道的,單元測試是以程式來驗證程式,也就是說,我要從外部來驗證一個函式是否正確,就撰寫單元測試函式,來呼叫我想驗證的函式,傳入固定的參數,我想驗證的函式理當也會有著同樣固定的回傳結果。藉由判斷回傳結果是否與預期的一致,我就可以驗證該函式是否正確。

但剛才說,這邊出了點問題,因為SplitMoney()這個方法在計算的過程中呼叫到了Convert這個函式:

//使用到外部函式(抓取匯率)
double rate = currencyConverter.Convert("USD", "TWD");

上面這個方法,抓取的是『即時』的匯率,而因為匯率是浮動的,這表示,當我們每一次呼叫這個函式時,即便傳入的,是同樣的參數,也可能得到不同的結果!

如此一來,該函式SplitMoney()就沒有可測試性(testability),因為我們無法去驗證它,是否在被修改後,還維持著一致的運算邏輯。

該如何解決前面提到的問題?

使用fake類別提高可測試性

我們先釐清問題在哪,顯然,問題的來源不是來自於SplitMoney()這個方法本身,而是來自於SplitMoney()這個方法執行的過程中,所呼叫的即時匯率抓取函式。

倘若,我們能夠在執行單元測試方法時,也就是運行掛載著[TestMethod()] 這個attribute的單元測試方法時,將SplitMoney()中抓取即時匯率的函式給換掉,改成永遠回傳固定值(例如台幣與美金的兌換永遠維持在 27:1),如此一來,我們的單元測試函式,就可以撰寫了,因為我們每次重複呼叫時,都可以得到一個穩定的預期結果值。

不過,我們在執行單元測試的時候,雖然抓取的匯率必須是固定的值,但一般呼叫的時候,則必須要保持是抓取到動態的匯率才行呀。

為了實現這個功能,我們可以針對SplitMoney()中所用到的抓取匯率的這個類別,設計一個相同的偽裝類別(Fake Class)。為了這麼做,我們先把抓取匯率的這個類別CurrencyConverter抽提出介面ICurrencyConverter,同時調整一下原本的CurrencyConverter類別,讓它繼承自介面ICurrencyConverter:

   public interface ICurrencyConverter
   {
     float Convert(string From, string To);
   }
   
   public class CurrencyConverter : ICurrencyConverter
   {
      public float Convert(string From, string To)
      {
         //…具體程式碼請參考 github
         // https://github.com/isdaviddong/HOL-UnitTestWithIoC_After
         return data;
      }
   }

然後,再繼承ICurrencyConverter介面設計出一個fake類別:

//建立fake類別
public class FakeCurrencyConverter : ICurrencyConverter
{
   public float Convert(string From, string To)
   {
      return 27.67222F;
   }
}

你會發現,我們在假類別中,把匯率固定在27.6,如此一來,倘若我們在單元測試方法中,可以讓SplitMoney()採用這個假類別FakeCurrencyConverter,而非使用真的CurrencyConverter類別去抓取即時匯率,那就可以在單元測試方法中,維持SplitMoney()回傳值的穩定性(冪等性)。如此一來,就可以正確的撰寫單元測試了。

那我們要怎麼讓單元測試中的程式碼在呼叫SplitMoney()方法的時候,採用假類別,而一般程式碼呼叫SplitMoney()方法的時候,卻又使用真的類別呢?

請注意,這時候,建構子注入就要出現了。

透過IoC與DI提高可測試性

就物件導向程式設計的概念來說,如果A方法對於B類別有依賴,我們可以幫B類別設計(重構出)一個介面C,將A方法程式碼中對於B類別的依賴,改寫成對介面C的依賴。

這其實,就是剛才我們做的事情。

我們對SplitMoney()所倚賴的類別CurrencyConverter進行重構,為它建立介面ICurrencyConverter,並且繼承該介面建立出一個類似CurrencyConverter類別的假類別FakeCurrencyConverter。

這樣有何好處?

如此一來,我們可以調整原本SplitMoney()方法的程式碼,把寫死依賴CurrencyConverter類別的這個狀況,改為依賴介面ICurrencyConverter:

ICurrencyConverter _CurrencyConverter;

public double SplitMoney(double USDAmount, int People)
{
   //var currencyConverter = new CurrencyConverter();
   //使用到外部函式(抓取匯率)
   double rate = _CurrencyConverter.Convert("USD", "TWD");
   //計算台幣總金額
   double Total = USDAmount * rate;
   //回傳一個人需要付多少錢(台幣)
   return Total / People;
  }

然後,在建立該類別的時候,把_CurrencyConverter先預設設定為CurrencyConverter類別即可。

這樣做的好處是,有需要時,我們可以動態替換抓取匯率的類別,將其改成fake類別,以便於固定抓取到的匯率回傳值。進而讓SplitMoney()方法的計算結果冪等,好讓我們可以撰寫單元測試。

因此,我們依照上面的邏輯,把程式碼改成底下這樣,實現所謂的建構子注入:

public class Financy
{
   //加入建構子注入
   ICurrencyConverter _CurrencyConverter;
   public Financy(ICurrencyConverter currencyConverter)
   {
      _CurrencyConverter = currencyConverter;
   }

   public Financy()
   {
      //預設狀況下,用標準類別
      _CurrencyConverter = new CurrencyConverter();
   }

   public double SplitMoney(double USDAmount, int People)
   {
      //var currencyConverter = new CurrencyConverter();
      //使用到外部函式(抓取匯率)
      double rate = _CurrencyConverter.Convert("USD", "TWD");
      //計算台幣總金額
      double Total = USDAmount * rate;
      //回傳一個人需要付多少錢(台幣)
      return Total / People;
   }
}

也就是說,我們在呼叫這個SplitMoney()方法前,在建立其所屬的類別Financy時,可以把將來要具體使用的抓取匯率的類別,以建構子參數的方式傳入,而不是寫死在SplitMoney()方法中。如此一來,我們就可以在運行單元測試的時候,採用fake類別:

[TestClass()]
public class FinancyTests
{
   [TestMethod()]
   public void SplitMoneyTest()
   {
      //注入測試用的fake類別實作
      Financy f = new Financy(new FakeCurrencyConverter());
      var CostByPeople = f.SplitMoney(100, 5);
      Assert.IsTrue(CostByPeople.ToString().StartsWith("553.444"));
   }
}

而運行一般程式的時候,採用正常的類別(當沒有傳入建構子參數,則預設使用一般類別):

Console.Write("請輸入金額(USD):");
var amount = int.Parse(Console.ReadLine()); //100
Console.Write("請輸入人數:");
var people = int.Parse(Console.ReadLine()); //5
Console.ReadLine();
//採用正常的類別
Financy f = new Financy();
var CostByPeople = f.SplitMoney(amount, people);

這樣一來,是不是讓原本不易撰寫測試程式的程式碼,變成可以輕易測試了呢? 這就是可測試性(testability)的提升。

如果你需要程式碼,請參考:
https://github.com/isdaviddong/HOL-UnitTestWithIoC_After


本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。

若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

2022年2月23日 星期三

Azure DevOps in Action - 脫離組織

你知道的,有些地方,進去很容易,脫離就有點困難。
我說的不是黑道堂口,也不是邪教異端,我說的是 Azure DevOps organization。

不只一次,我被問過這個問題:『當被加入一個Azure DevOps組織之後,要怎麼離開呀?』

沒錯,你尋遍整個portal,沒有離開的選項。也就是說,倘若該組織的admin不把你移除,你似乎永遠會屬於某個組織,這導致於,每次你登入的時候,左方選單裡面就有一大多你根本已經不會去用的組織站台:
enter image description here

這些站台多還不打緊,它還讓我無法建立新的組織:
enter image description here

這使得我下定決心,要脫離組織的綑綁。
尋找了一會兒,發現,原來還是可以脫離組織的,但這功能不在portal上,而是在 https://aex.dev.azure.com/ 這個頁面:
enter image description here

在這個畫面上會列出你的帳號參與了哪些組織。
原來,在這個畫面,只要展開你不是owner的組織,右下角就會有一個 Leave 聯結按紐,把它給點下去,出現了警告訊息:
enter image description here

但,這就是我要的呀。
點選 Leave 後,徹底脫離組該織的所有的掌控與專案了。

2022年2月11日 星期五

tech and business predictions and expectations for 2022

In 2021, the original expectation of the whole world was that life could return to normal through the launch of a vaccine, but this idea was once again interrupted by uncertain factors.

Omicron allows the world to face a new wave of challenges, and at the same time, it also makes companies start to think about how to respond if they need to coexist with the virus for a long time. So, in 2022, we’ll have chances of seeing these things happening underneath.

  1. More remote collaborative operation modes will appear
    IT vendors have promoted online conferencing software for many years in the past. In these days of the epidemic, there has been subversive growth. After breaking through this barrier, more technologies and tools for remote cooperation will emerge. besides, 5G and the artificial intelligence will make the future “cooperation” full of imagination. In the past, most remote collaborations were people-to-people collaborations – co-finishing a project, co-authoring an article, co-developing a piece of code, but in the future, AI tools in the cloud may also join to the collaboration. Become a virtual employee in the enterprise. Act as a real-time translator, secretary, or even a consultant to provide information or advice while meeting remotely online.

  2. There will be changes in management methods, and the need for mid-level managers will be reduced
    Over the past year, companies have gradually realized that not all work has to be done in the office. Even if some employees are working from home, the proper use of information tools can also exert their output value. In the past, the practice of relying on middle-level managers for communication, coordination, or management of grass-roots personnel will face challenges. Enterprises will adopt more virtualized office environments, flatter organizations, fewer reporting layers, and more use of IT tools for direct communication, all of which will happen more dramatically in 2022. As a result, It will reduce the dependence of enterprises on middle-level managers, and the related workflow automation software tools will be paid more attention.

  3. The implementation of enterprise automation and productivity tools will be accelerated
    Over the past few years, we have seen DevOps tools effectively automate the software development process with great success across industries. Due to the unstable situation of the epidemic, companies will need to introduce more similar process automation tools in different areas. In the past two years, global users’ demand for online conferencing software has increased significantly. If the epidemic continues, various process automation tools, remote collaboration tools, digital dashboard and performance management tools will become important support for enterprises to face challenges in the future.

The world has entered its second year in the face of Covid-19. In the past two years, whether it is the information industry or the entire society, it has suffered a considerable impact, AI and the virtual reality technology continues to develop rapidly. In 2022 , will have the opportunity to apply it in medical, education, psychological counseling and counseling and other scenarios. These will be our challenges and opportunities in the coming year.

熱門文章