99国产精品欲av蜜臀,可以直接免费观看的AV网站,gogogo高清免费完整版,啊灬啊灬啊灬免费毛片

網(wǎng)易首頁(yè) > 網(wǎng)易號(hào) > 正文 申請(qǐng)入駐

從登錄驗(yàn)證到抽卡存儲(chǔ),一站式打造安全高效的云端游戲服務(wù)端邏輯

0
分享至

新年將至,Unity 中國(guó)祝所有小伙伴和家人們新年快樂(lè)!2024 年,。新的一年里,愿所有小伙伴在吉運(yùn)常伴,創(chuàng)意無(wú)限,不斷取得豐碩成果!點(diǎn)擊下圖,領(lǐng)取蛇年限定紅包封面~

本文分享 UOS 實(shí)用教程,幫助大家在假期輕松提升技能。

之前,我們已經(jīng)為大家分享了一篇教程《》,詳細(xì)介紹了如何使用 Unity Online Services(UOS)提供的 C# 云函數(shù)服務(wù),在 Unity 開(kāi)發(fā)環(huán)境中快速實(shí)現(xiàn)和部署聯(lián)網(wǎng)游戲的服務(wù)器端邏輯。在那篇教程中,我們展示了如何使用云數(shù)據(jù)庫(kù)服務(wù) CRUD Storage 來(lái)存儲(chǔ)諸如玩家信息和抽卡記錄等數(shù)據(jù)。

今天,我們將再次以“抽卡”功能為例,但深入探討的技術(shù)內(nèi)容有所不同

在本篇教程中,我們將借助 UOS 提供的Passport-Login服務(wù) 來(lái)完成用戶的登錄驗(yàn)證流程。隨后,我們將利用云函數(shù) Func-Stateless來(lái)部署抽卡功能的服務(wù)器端邏輯代碼。最后,這些抽卡數(shù)據(jù)將通過(guò)CRUD-Save服務(wù) 實(shí)現(xiàn)云端存儲(chǔ),確保數(shù)據(jù)的持久性和安全性。

教程中涉及 UOS 服務(wù)包括:

  • 云函數(shù)服務(wù) Func Stateless (C#):用于部署抽卡服務(wù)端邏輯代碼

  • 云存檔服務(wù) CRUD Save:用于存儲(chǔ)抽卡記錄等數(shù)據(jù)

  • 玩家通行證服務(wù) Passport Login:實(shí)現(xiàn)用戶登錄及實(shí)名認(rèn)證

云函數(shù)服務(wù) Func Stateless

在保障游戲或應(yīng)用安全性的重要考量下, 確保關(guān)鍵邏輯和數(shù)據(jù)的權(quán)威性處理僅在服務(wù)器端執(zhí)行顯得尤為重要 。這是因?yàn)?客戶端的代碼包體容易被破解或篡改 ,從而引發(fā)不公平競(jìng)爭(zhēng)、數(shù)據(jù)泄露等安全問(wèn)題。UOS Func Stateless (C#) 云函數(shù)以其高效、靈活、安全且成本低的特點(diǎn),為游戲開(kāi)發(fā)者提供了一個(gè)理想的服務(wù)端邏輯解決方案,助力他們?cè)诳焖俚氖袌?chǎng)環(huán)境中保持競(jìng)爭(zhēng)優(yōu)勢(shì)。

  • Func Stateles s 支持在本地開(kāi)發(fā)環(huán)境中直接調(diào)試云函數(shù) ,無(wú)需部署到云端即可驗(yàn)證邏輯的正確性,降低了調(diào)試難度,加快了開(kāi)發(fā)迭代速度。

  • Func Stateless 能夠 自動(dòng)將服務(wù)端邏輯代碼打包并部署到云端 ,簡(jiǎn)化了部 署流程,減少了人為錯(cuò)誤,讓開(kāi)發(fā)者更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。

云存檔服務(wù) CRUD Save

借助 UOS Save 所提供的全面而專業(yè)的 玩家數(shù)據(jù)存儲(chǔ)、檢索及 管理服務(wù) ,開(kāi)發(fā)者能夠極為便捷地為廣大游戲玩家打造出一個(gè)跨越不同平臺(tái)與設(shè)備的、安全且高度可用的游戲存檔系統(tǒng)

這一服務(wù)不僅確保了玩家能夠在任何時(shí)間、任何地點(diǎn)無(wú)縫地繼續(xù)他們的游戲進(jìn)程,而且通過(guò)嚴(yán)格的數(shù)據(jù)安全保障措施,讓玩家的游戲數(shù)據(jù)始終處于嚴(yán)密的保護(hù)之下。同時(shí),其高可用性的設(shè)計(jì)也保證了玩家數(shù)據(jù)的實(shí)時(shí)同步與持久存儲(chǔ),為玩家?guī)?lái)了更加流暢、穩(wěn)定的游戲體驗(yàn)。

玩家通行證服務(wù) Passport

Passport 是 UOS 官方提供的玩家通行證服務(wù),包括玩家登錄系統(tǒng) (Login),以及與玩家相關(guān)的眾多游戲功能 (Feature)。

  • Passport Login 是一個(gè)可以開(kāi)箱即用的玩家登錄系統(tǒng),通過(guò)非常簡(jiǎn)便的集成方式, 便可以獲得如下功能: 靈活可 配的 UI 登錄界面,包含用戶協(xié)議、登錄、實(shí)名認(rèn)證; 支持手機(jī)號(hào)登錄,以及微信,QQ, Apple ID 等 主流第三方 OAuth 登錄;可以配置游戲服務(wù)器,以及管理游戲角色。

  • Passport Feature 是以玩家為中心,包含了在線游戲中各種常見(jiàn)的功能。包括:排行榜、公會(huì)、游戲禮包、防沉迷系統(tǒng)、經(jīng)濟(jì)系統(tǒng)、郵件系統(tǒng)、成就系統(tǒng)、戰(zhàn)令系統(tǒng)、公告、任務(wù)系統(tǒng)。

教程視頻

教程學(xué)習(xí)大綱

  1. 在 UOS 官網(wǎng)下載示例項(xiàng)目工程

  2. 創(chuàng)建 UOS App 并啟用 Func-Stateless / CRUD-Save / Passport-Login 服務(wù)

  3. 設(shè)置云函數(shù)的安裝和配置目錄

  4. 在本地模式下調(diào)試并運(yùn)行項(xiàng)目,通過(guò) Passport 實(shí)現(xiàn)用戶登陸

  5. 通過(guò) CRUD Save 實(shí)現(xiàn)用戶抽卡數(shù)據(jù)的云存檔

  6. 上傳和部署云函數(shù),切換到遠(yuǎn)程調(diào)用模式下測(cè)試云函數(shù)的調(diào)用

教程案例工程源文件

教程內(nèi)學(xué)習(xí)用到的項(xiàng)目工程文件可以通過(guò)以下方式進(jìn)行下載:

UOS 官網(wǎng)示例教程中在線下載:

https://uos.unity.cn/doc/func/stateless/csharp-tutorial#prepare

教程操作步驟

接下來(lái)讓我們來(lái)看看 Func Stateless 云函數(shù)結(jié)合 CRUD Save 云存檔在抽卡項(xiàng)目中的具體用法吧!

1. 在 UOS 官網(wǎng)下載示例項(xiàng)目工程

1.1 下載項(xiàng)目工程文件

首先前往上方提供的UOS 官網(wǎng)的教程鏈接地址,在「Func」的文檔左側(cè)頁(yè)面選擇「Stateless」下方的「示例教程(C#)」,在「準(zhǔn)備工作」模塊點(diǎn)擊按鈕「DEMO(CLOUDSAVE PASSPORT)工程」,就可以下載抽卡 Demo 的項(xiàng)目工程了。

1.2 解壓縮項(xiàng)目工程文件

在下載完項(xiàng)目工程壓縮包以后,請(qǐng)解壓縮下圖中的項(xiàng)目工程文件。

1.3 加載項(xiàng)目工程

然后打開(kāi) Unity Hub,選擇電腦上已經(jīng)安裝過(guò)的 Unity 編輯器版本,來(lái)打開(kāi)剛剛下載好的項(xiàng)目工程,教程這里使用的是Unity2022.3.48f1c1 版本

打開(kāi)項(xiàng)目工程以后,在項(xiàng)目的 Project 窗口中,找到「Assets/Scenes/Demo.unity」場(chǎng)景并打開(kāi)。

點(diǎn)擊「Import TMP Essentials」導(dǎo)入 TextMeshPro 的字體資源。

2.綁定 UOS App 并開(kāi)啟服務(wù)

溫馨提示:當(dāng)前項(xiàng)目中已安裝好 UOS Launcher,不需要再次安裝了。大家可以參考之前的的公眾號(hào)文章教程,來(lái)為你當(dāng)前的項(xiàng)目綁定你創(chuàng)建好的 UOS App 。

2.1 綁定 UOS App

點(diǎn)擊 Launcher 面板的「LinkApp」按鈕,在彈出窗口中選擇「By Unity project」,在「Select organization」這里選擇一個(gè)自己的項(xiàng)目組織,然后在「Select project 」選項(xiàng)這里,我們選擇「Create a new project 」,自行設(shè)置修改項(xiàng)目名字「Project name」。

教程中,我們就先選擇綁定創(chuàng)建好的 FuncStatelessAndSaveDemo 應(yīng)用了。

2.2 開(kāi)啟 Func-Stateless 服務(wù)

在編輯器內(nèi) Unity Online Services 窗口的下拉服務(wù)列表中,找到「Func - Stateless」,點(diǎn)擊「Enable」按鈕來(lái)一鍵開(kāi)啟服務(wù), 進(jìn)入 UOS 網(wǎng)頁(yè)端可以看到,此時(shí)默認(rèn)云函數(shù)的腳本語(yǔ)言是基于 Dotnet 的運(yùn)行環(huán)境的。

如果你是在 UOS 網(wǎng)頁(yè)端為當(dāng)前 App 開(kāi)啟 Func-Stateless 服務(wù)的話,需要自行手動(dòng)在腳本語(yǔ)言的下拉選項(xiàng)框中選擇Dotnet環(huán)境 。

當(dāng)前項(xiàng)目中已經(jīng)安裝過(guò) Func - Stateless 服務(wù) SDK,無(wú)需再次安裝了。如果你自己的項(xiàng)目中沒(méi)安裝過(guò)的話,可以點(diǎn)擊「Install SDK」的按鈕進(jìn)行安裝。

2.3 開(kāi)啟 CRUD-Save 服務(wù)

接著繼續(xù)在UOS Launcher 的下拉服務(wù)窗口列表中,找到CRUD-Save」,點(diǎn)擊「Enable」開(kāi)啟服務(wù),當(dāng)然你也可以在 UOS 網(wǎng)站上直接開(kāi)啟服務(wù)。

當(dāng)前項(xiàng)目中也是已經(jīng)安裝過(guò) CRUD-Save 服務(wù)的 SDK了,無(wú)需再次安裝了。

2.4 開(kāi)啟 Passport-Login 服務(wù)

接著繼續(xù)在 UOS Launcher 的下拉服務(wù)窗口列表中,找到「Passport Login」,點(diǎn)擊「Enable」開(kāi)啟服務(wù)。

當(dāng)前項(xiàng)目中也是已經(jīng)安裝過(guò) Passport Login 服務(wù)的 SDK了,無(wú)需再次安裝了。

3.設(shè)置云函數(shù)的安裝和配置目錄

3.1 導(dǎo)入 NuGetForUnity 工具

在 Unity Editor 菜單欄中先點(diǎn)擊「UOS -> Func Stateless -> Open Panel」按鈕,打開(kāi) Func Stateless Tool 面板。

然后再點(diǎn)擊「UOS -> Func Stateless -> Import NuGetForUnity」,來(lái)導(dǎo)入 UOS 版本的 NuGetForUnity 工具。

3.2 修改云函數(shù)的安裝和配置目錄

導(dǎo)入完成后,點(diǎn)擊菜單欄中「NuGet -> Preferencs」打開(kāi)配置界面。修改Packages Install Path 為云函數(shù)所在目錄下的 Packages 目錄;Packages Config Path 為云函數(shù)所在目錄。

在這里我們就先使用給定的默認(rèn)設(shè)置目錄了,大家有需要自行修改

登錄功能模塊的 UI 界面和腳本,我們使用的是 UOS 的 Passport 服務(wù)為用戶提供的示例模板,在之前點(diǎn)擊導(dǎo)入「PassportUI」資源的時(shí)候已經(jīng)導(dǎo)入過(guò)了,可以直接在項(xiàng)目中使用。

在這里我們使用的是手機(jī)號(hào)短信驗(yàn)證碼的方式登錄的, 由于 Passport 的「登錄配置」默認(rèn)是開(kāi)啟了真實(shí)短信驗(yàn)證和實(shí)名認(rèn)證的。所以,這里請(qǐng) 輸入真實(shí)的手機(jī)號(hào)和短信驗(yàn)證碼 來(lái)完成登錄。

當(dāng)輸入完短信驗(yàn)證碼后,可以在 Console 控制臺(tái)看到輸出的日志信息:「完成登錄」

日志信息是在 UIController.cs 腳本中調(diào)用輸出的,找到場(chǎng)景中的 UIController 對(duì)象,可看到掛載的 UIController.cs 腳本:

進(jìn)入 UIController.cs 腳本查看下代碼,在 Start 方法中會(huì)調(diào)用 Passport SDK 進(jìn)行 UI 初始化的方法 Init ,方法中需要傳入配置 _config 和回調(diào)函數(shù) _callback

_config 是 Passport SDK 進(jìn)行初始化時(shí)的相關(guān)配置:來(lái)設(shè)置是否自動(dòng)旋轉(zhuǎn)屏幕方向、是否通過(guò)自行調(diào)用 Login 函數(shù)啟動(dòng)登錄面板,以及設(shè)置 UI 風(fēng)格主題是深色還是淺色等等。

_callback 是 Passport SDK 的回調(diào)函數(shù):Passport 中已經(jīng)封裝注冊(cè)好了,在用戶拒絕協(xié)議、完成登錄、完成所有流程、用戶登出這幾個(gè)狀態(tài)下的回調(diào)事件。

所以當(dāng)用戶短信驗(yàn)證通過(guò)后,會(huì)自動(dòng)進(jìn)入LoggedIn狀態(tài)下的回調(diào)事件,輸出了日志「完成登錄」

public class UIController : MonoBehaviour
{
// sdk 配置(Config 是 SDK 初始化時(shí)的配置)
private readonly PassportUIConfig _config = new()
{
AutoRotation = true, // 是否開(kāi)啟自動(dòng)旋轉(zhuǎn),默認(rèn)值為 false。
InvokeLoginManually = false, // 是否通過(guò)自行調(diào)用 Login 函數(shù)啟動(dòng)登錄面板,默認(rèn)值為 false。
Theme = PassportUITheme.Dark, // 風(fēng)格主題配置。
UnityContainerId = "unity-container" // WebGL 場(chǎng)景下 Unity 實(shí)例容器 Id。
};

// sdk 回調(diào)
private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
}

}
private void Start()
{
// 調(diào)用 SDK
PassportUI.Init(_config, _callback);
}
 }

然后會(huì)需要進(jìn)行實(shí)名身份認(rèn)證:

當(dāng)實(shí)名驗(yàn)證成功后,會(huì)看到控制臺(tái)日志輸出:「完成所有流程」。

此時(shí)會(huì)自動(dòng)回調(diào)PassportEvent.Completed 狀態(tài)下的事件。

4.2 修改登錄配置

接著可以點(diǎn)擊 Passport Login 旁邊的「Developer portal」按鈕,進(jìn)入 UOS 網(wǎng)頁(yè)端的 Passport 模塊來(lái)自行修改「登錄配置」中的設(shè)置。

點(diǎn)擊選中「模擬短信驗(yàn)證」,這樣驗(yàn)證碼直接輸入「 111111」即可;取消選中「實(shí)名認(rèn)證」,則暫時(shí)不再驗(yàn)證用戶的身份信息。

修改完配置后,可以再次回到編輯器測(cè)試下效果。點(diǎn)擊運(yùn)行游戲,選擇「退出登錄」,然后「使用新的賬號(hào)登錄」,再重新輸入一個(gè)手機(jī)號(hào),然后輸入短信驗(yàn)證碼 111111 ,就可以成功登陸了,同時(shí)也跳過(guò)了身份驗(yàn)證,直接可以進(jìn)入游戲。Console 控制臺(tái)也能看到對(duì)應(yīng)的日志輸出。

4.3 選擇角色

登陸完成后,會(huì)選擇一個(gè)角色進(jìn)入游戲。 在腳本的 _callback 方法的Completed 狀態(tài)下,看到會(huì)調(diào)用選擇角色的方法SelectPersona

private async void _callback(PassportEvent e)
{
// event: 不同情況下的回調(diào)事件,詳情可以參考下面的回調(diào)類型。
switch (e)
{
case PassportEvent.RejectedTos:
Debug.Log("用戶拒絕了協(xié)議");
break;
case PassportEvent.LoggedIn:
Debug.Log("完成登錄");
break;
case PassportEvent.Completed:
Debug.Log("完成所有流程");
await SelectPersona();
break;
case PassportEvent.LoggedOut:
Debug.Log("用戶登出");
break;
    }
}

在方法 SelectPersona 中,會(huì)先調(diào)用PassportSDK.Identity.GetRealms()方法獲取域列表,然后從域列表中根據(jù)需要選擇一個(gè)域,在這里我們暫時(shí)先使用域列表的第一個(gè)域。

大家也可以手動(dòng)填寫你想要使用的某一個(gè)域。我們進(jìn)入 UOS 網(wǎng)頁(yè)端 Passport 的「服務(wù)器」模塊,可以看到默認(rèn)已經(jīng)幫我們創(chuàng)建了一個(gè)域了。點(diǎn)擊復(fù)制域的 UUID,然后賦值給腳本中的域變量 realmID 即可。

// 選擇域
//var realms = await PassportSDK.Identity.GetRealms(); // 獲取域列表
//var realmID = realms[0].RealmID; // 根據(jù)需要自行選擇域
var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填寫固定的 RealmID 而不是動(dòng)態(tài)獲取

修改完代碼后,可以再次運(yùn)行,是可以正常進(jìn)入游戲的。然后我們需要選擇一個(gè)角色,先通過(guò)調(diào)用GetPersonas() 方法來(lái)獲取到角色列表

由于 Demo 示例中我們默認(rèn)啟用的配置是:一個(gè)用戶單服務(wù)器下只能創(chuàng)建一個(gè)角色,并沒(méi)有開(kāi)啟單服務(wù)器下多角色的功能。所以目前是一個(gè)手機(jī)號(hào)對(duì)應(yīng)一個(gè)用戶 ID,一個(gè)用戶 ID 對(duì)應(yīng)一個(gè)角色 ID。

  • 如果角色列表中沒(méi)有任何角色,則會(huì)調(diào)用 CreatePersona 方法,在指定的域 realmID 中來(lái)創(chuàng)建一個(gè)角色賦值給 persona 變量。

  • 如果角色列表中已經(jīng)有角色的話,會(huì)直接選擇已創(chuàng)建過(guò)的角色 personas[0] 賦值給 persona 變量。最后,再通過(guò)調(diào)用 SelectPersona 方法來(lái)指定選擇的角色。


// 選擇角色
private async Task SelectPersona()
{
// 選擇域
//var realms = await PassportSDK.Identity.GetRealms(); // 獲取域列表
//var realmID = realms[0].RealmID; // 根據(jù)需要自行選擇域
    var realmID = "543b59d1-3008-4028-8591-83167e82feb0"; // 也可以填寫固定的 RealmID 而不是動(dòng)態(tài)獲取

// 獲取(或創(chuàng)建)與選擇角色
Persona persona = null;
var personas = await PassportSDK.Identity.GetPersonas(); // 獲取角色列表
if (!personas.Any())
{
// 若沒(méi)有角色,則新建角色
persona = await PassportSDK.Identity.CreatePersona("YourDisplayName", realmID);
}
else
{
// 若有角色,則選擇第一個(gè)角色
persona = personas[0];
}
// 選擇角色
await PassportSDK.Identity.SelectPersona(persona.PersonaID);
}

4.4 創(chuàng)建一個(gè)新的域

用戶也可自行創(chuàng)建一個(gè)新的域來(lái)使用。在 Passport 的【服務(wù)器】管理頁(yè)面,點(diǎn)擊【創(chuàng)建新的域】,自己設(shè)置下名稱,點(diǎn)擊【創(chuàng)建】即可。

然后可以將新創(chuàng)建的域的 UUID 賦值給腳本中的變量 realmID ,再次進(jìn)行測(cè)試后,回到 UOS 網(wǎng)頁(yè)端,點(diǎn)擊查看域的詳情,可以看到域中已經(jīng)有創(chuàng)建的角色信息了。

4.5 進(jìn)入游戲場(chǎng)景,獲取用戶登錄信息

登陸成功后,會(huì)看到下面的游戲畫面,我們先來(lái)看看【進(jìn)入游戲】的 UI 對(duì)象上綁定的事件。

場(chǎng)景中的 MainUI 對(duì)象上掛載了 MainUIController.cs 腳本,當(dāng)我們點(diǎn)擊【進(jìn)入游戲】的按鈕時(shí),會(huì)執(zhí)行 MainUIController.cs 腳本中的 StartGame 的方法。

點(diǎn)擊【進(jìn)入游戲】按鈕,可以進(jìn)入游戲場(chǎng)景:

同時(shí),Console 控制臺(tái)會(huì)看到有下面的日志輸出信息。

首先來(lái)看看輸出的日志:“currentUserId, use as save name”,說(shuō)明執(zhí)行了 StartGame 方法 。會(huì)首先通過(guò) PassportSDK 來(lái)獲取到當(dāng)前角色所屬的用戶ID。

public async void StartGame()
{
try
{
ShowLoading(true);
// await NetworkManager.Login(username, password);
var userId = PassportSDK.CurrentPersona.UserID;
Debug.Log($"currentUserId, use as save name: {userId}");
await NetworkManager.Login(userId);
}
catch (Exception e)
{
Debug.LogException(e);
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

剛才控制臺(tái)對(duì)應(yīng)輸出的日志信息還有:“l(fā)oginResult: 1000000001, 106104, 467”。因?yàn)榇a中拿到 UserID 后,異步調(diào)用了NetworkManager.Login方法。

在 Login 方法中,會(huì)先通過(guò)調(diào)用云函數(shù)腳本 LoginService 中的云函數(shù) Login 來(lái)獲取到登陸結(jié)果 loginResult。然后再通過(guò)事件調(diào)用,將用戶的昵稱(Nickname)、金幣數(shù)(Coins)、鉆石數(shù)(Diamonds)的信息,同步更新到 UI 界面上。

稍后我們會(huì)展開(kāi)來(lái)詳細(xì)講解:云函數(shù)腳本 LoginService 和 ActionService 的。

public async Task Login(string username)
{
try
{
var loginService = new LoginService();
var loginResult = await loginService.Login(username);
if (!loginResult.Ok)
{
throw new Exception(loginResult.Message);
}
_saveId = loginResult.SaveId;
_user = loginResult.User;
_as = new ActionService();

Debug.Log($"loginResult: {loginResult.User.Nickname}, {loginResult.User.Coins}, {loginResult.User.Diamonds}");

// invoke ui update
onLogin.Invoke(new Account
{
Nickname = loginResult.User.Nickname,
Coins = loginResult.User.Coins,
Diamonds = loginResult.User.Diamonds
});
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

5.云函數(shù)結(jié)合 CRUD Save 實(shí)現(xiàn)用戶抽卡數(shù)據(jù)的云存檔

接下來(lái)看看項(xiàng)目中如何使用 CloudSave 實(shí)現(xiàn)讀檔和存檔的!

5.1查看網(wǎng)頁(yè)端的云存檔文件

運(yùn)行游戲點(diǎn)擊抽卡后,用戶抽卡的相關(guān)結(jié)果數(shù)據(jù)信息,其實(shí)已經(jīng)通過(guò) UOS 的 CRUD-Save 服務(wù)實(shí)現(xiàn)云存檔了。

我們進(jìn)入 UOS 網(wǎng)頁(yè)端CRUD Save的【存檔管理】頁(yè)面,可以看到當(dāng)前登錄的用戶,然后點(diǎn)擊最右邊的【詳情】按鈕。

可以在彈出的界面中查看【存檔詳情】,點(diǎn)擊【下載存檔】

打開(kāi)存檔文件后,可以查看文件中存儲(chǔ)的抽卡的信息。

用戶也可以在網(wǎng)頁(yè)上點(diǎn)擊【對(duì)玩家隱藏存檔】或者【刪除存檔】,現(xiàn)在我們演示下點(diǎn)擊頁(yè)面上的【刪除存檔】按鈕,則會(huì)清空該存檔 ID 對(duì)應(yīng)的一整條信息的。

此時(shí)如果再次運(yùn)行游戲,可以看到金幣數(shù)和鉆石數(shù)將顯示為 0,英雄列表和背包列表也都為空了。

接下來(lái)我們看看云存檔的功能具體是如何實(shí)現(xiàn)的!

5.2初始化 CloudSaveSDK

在 MainUIController.cs 腳本的 Start 方法中,會(huì)調(diào)用InitializeAsync方法,該方法會(huì)使用 UOS Launcher 中填寫的 UOS APP 信息來(lái)實(shí)現(xiàn)對(duì) CloudSaveSDK 的初始化。

private async void Start()
{
    //此處省略其它代碼行......

try
{
ShowLoading(true);
await CloudSaveSDK.InitializeAsync();
ShowLoading(false);
}
catch (Exception e)
{
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

5.3登錄云函數(shù)結(jié)合 CloudSaveSDK 實(shí)現(xiàn)讀取已存檔的用戶信息

然后點(diǎn)擊菜單欄「UOS -> Func Stateless -> Open Panel」按鈕,打開(kāi)「Func Stateless Tool」窗口。

在打開(kāi)的工具的窗口中,可以看到當(dāng)前項(xiàng)目中的 2 個(gè)云函數(shù)類LoginService 和 ActionService。我們先不上傳云函數(shù),先在本地進(jìn)行調(diào)試運(yùn)行項(xiàng)目

雙擊云函數(shù)「Login」,就可以打開(kāi)云函數(shù)所在腳本了。 我們來(lái)分析下云函數(shù) Login 的代碼,會(huì)先通過(guò)調(diào)用 CloudSaveSDK 封裝的方法ListAllAsync 來(lái)列出當(dāng)前用戶的所有存檔元數(shù)據(jù)信息,存儲(chǔ)在變量 saveItems 中。

[CloudService]
public class LoginService
{
[CloudFunc]
public async Task Login( string username)
{
            // 此處省略其它代碼行......
            
            var saveItems = await CloudSaveSDK.Instance.Files.ListAllAsync();
         }
     }

然后判斷下如果 saveItems 集合長(zhǎng)度為 0,說(shuō)明當(dāng)前用戶沒(méi)有存檔文件,是新用戶第一次登錄,那么我們就會(huì)調(diào)用CreateAsync 方法來(lái)創(chuàng)建一個(gè)存檔文件

創(chuàng)建存檔文件的時(shí)候,需要傳入三個(gè)參數(shù),存檔名稱 username、字節(jié)數(shù)組類型的存檔文件 data、存檔選項(xiàng) options。

  • 我們創(chuàng)建了一個(gè)新用戶對(duì)象 newUser,然后通過(guò)封裝的SerializeToByteArray方法,將 newUser 的屬性信息轉(zhuǎn)換成字節(jié)數(shù)組變量 data,作為第二個(gè)參數(shù)傳遞到 CreateAsync 方法中。

  • 在存檔選項(xiàng) options 中,設(shè)置了當(dāng)前云存檔的模式為單存檔 ProgressType.Linear,如果你要在你的項(xiàng)目中使用多存檔模式的話,可以使用 ProgressType.Multi。

  • 大家可以自行查看封裝的存檔選項(xiàng)類 CreateOptions,根據(jù)需要自行傳入更多的參數(shù)。

  • 最后會(huì)將用戶登錄的結(jié)果返回。


if (saveItems.Count == 0)
{
var newUser = new User
{
Username = username,
Nickname = username + Helper.GetRandomEmoji(),
Diamonds = 500,
Coins = 0,
LuckPoints = 0,
DrawCounts = 0,
DrawPool = Helper.NewDrawPool(),
Heroes = new Dictionary (),
Props = new Dictionary ()
};

var data = SerializeHelper.SerializeToByteArray(newUser);
var options = new CreateOptions
{
ProgressType = ProgressType.Linear
};
var saveId = await CloudSaveSDK.Instance.Files.CreateAsync(username, data, options);
return new LoginResult
{
Ok = true,
SaveId = saveId,
User = newUser
};
}

如果當(dāng)前用戶已存在存檔文件,則驗(yàn)證下登錄用戶的密碼,然后調(diào)用LoadBytesAsync方法,根據(jù)用戶的存檔 ID 來(lái)獲取存檔文件內(nèi)容,賦值給變量 saveData。

然后通過(guò)封裝的反序列化DeserializeFromByteArray方法將字節(jié)數(shù)組變量 saveData 轉(zhuǎn)換成 User 類型,賦值給 user 變量,最后再將用戶登錄的結(jié)果返回。

// 驗(yàn)證密碼
var saveItem = saveItems[0];
// 讀取存檔
var saveData = await CloudSaveSDK.Instance.Files.LoadBytesAsync(saveItem.SaveId);
var user = SerializeHelper.DeserializeFromByteArray (saveData);
return new LoginResult
{
Ok = true,
SaveId = saveItem.SaveId,
User = user
};

5.4抽卡云函數(shù)結(jié)合 CloudSaveSDK 實(shí)現(xiàn)讀檔和存檔

當(dāng)我們點(diǎn)擊抽卡按鈕時(shí),抽到的金幣、鉆石、角色、物品等都會(huì)更新到 UI 面板上,也會(huì)同步更新到云存檔文件中的。

現(xiàn)在我們?cè)俅芜\(yùn)行游戲,然后找到場(chǎng)景中的抽卡的 Button 按鈕,可以看到按鈕響應(yīng)的是 MainUIController 腳本中的DrawCard方法。

下面的示例代碼給我們展示的是抽卡的方法(DrawCard)的客戶端的邏輯代碼

public async void DrawCard(int count = 1)
{
ShowLoading(true);
try
{
await NetworkManager.DrawCard(count);
}
catch (Exception e)
{
Debug.Log(e.Message);
ShowLoading(false);
MessageUI.Show(e.Message);
}
}

在 NetworkManager.DrawCard 代碼中,會(huì)調(diào)用ActionService.cs 腳本中的抽卡云函數(shù)(Draw)來(lái)獲取抽卡的結(jié)果

private ActionService _as;

public async Task DrawCard(int count)
{
try
{
        var drawResult = await _as.Draw(_saveId, count);    
        //此處省略其它代碼行......
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

下面我們來(lái)看看抽卡云函數(shù)(Draw)的代碼吧,代碼中實(shí)現(xiàn)了從卡池中抽取卡,并返回抽取的結(jié)果。

大家在這里先思考一個(gè)問(wèn)題:為什么需要把邏輯放在云函數(shù)里面呢?

原因是客戶端包體可能會(huì)被破解,需要把扣除資源(扣鉆石)這種邏輯放在云端,這樣才安全,否則客戶端被破解后,可以在客戶端無(wú)限抽卡了。

我們接著來(lái)看下代碼邏輯:

  • 在方法中先通過(guò)調(diào)用 CloudSaveSDK 封裝的方法 LoadBytesAsync ,根據(jù)用戶的存檔 ID 來(lái)獲取到存檔文件內(nèi)容 saveData。

  • 然后再通過(guò)封裝的反序列化 DeserializeFromByteArray 方法,將字節(jié)數(shù)組類型的結(jié)果轉(zhuǎn)換成 User 類型賦值給了變量 user,獲取到了當(dāng)前 user 的信息。

  • 接著開(kāi)始進(jìn)行抽卡,中間這段代碼是抽卡的邏輯代碼,大家有需要可以自行查看代碼。

  • 抽卡結(jié)束后會(huì)調(diào)用UpdateAsync 方法來(lái)更新存檔數(shù)據(jù)。更新存檔時(shí)需要傳入 2 個(gè)參數(shù),分別是存檔 ID 和更新存檔選項(xiàng) options。 在更新存檔文件時(shí) UpdateOptions 有不同的方式,可以是 ByFilePath、ByFileStream 或者 ByFileBytes。 在這里,我們通過(guò)UpdateFileWay.ByFileBytes 字節(jié)文件的方式來(lái)更新,而當(dāng)前用戶的信息會(huì)以 byte[] 數(shù)組的形式存在變量 data 中。

  • 最后該方法再將用戶抽卡的結(jié)果信息返回。


[CloudFunc]
public async Task Draw( string saveId, int count)
{
Debug.Log("call to draw");
// read
var saveData = await CloudSaveSDK.Instance.Files.LoadBytesAsync(saveId);
var user = SerializeHelper.DeserializeFromByteArray (saveData);

if (count > user.Diamonds)
{
return new DrawResult
{
Ok = false,
Message = "鉆石不夠"
};
}

// start to draw
        // 此處省略其它代碼行......

// write
user.Diamonds += diamonds - count;
user.Coins += coins;
user.LuckPoints += luckPoints;
user.DrawCounts += count;
user.DrawPool = pool;
user.Heroes = heroes;
user.Props = props;
var data = SerializeHelper.SerializeToByteArray(user);
var options = new UpdateOptions
{
File = new FileOptions
{
UpdateFileWay = UpdateFileWay.ByFileBytes,
FileBytes = data,
}
};
Debug.Log(saveId);
await CloudSaveSDK.Instance.Files.UpdateAsync(saveId, options);
return new DrawResult
{
Ok = true,
Items = res,
User = user
};
}
}

通過(guò)云函數(shù) Draw 抽取卡片后,在 NetworkManager.cs 腳本的 DrawCard 方法中會(huì)更新本地緩存,同時(shí)處理抽卡結(jié)果

  • 抽卡成功后,會(huì)將 _user 變量賦值為更新后的用戶信息 drawResult.User。

  • 同時(shí)將抽取到的卡片列表 drawResult.Items 轉(zhuǎn)換為一個(gè)新的集合對(duì)象 listResult,來(lái)存儲(chǔ)抽卡結(jié)果。

  • 通過(guò) onDrawCard.Invoke 方法的調(diào)用,通知注冊(cè)到 onDrawCard 事件的監(jiān)聽(tīng)器,在用戶界面上顯示新抽取的卡片的信息。


public async Task DrawCard(int count)
{
try
{
var drawResult = await _as.Draw(_saveId, count);
// update cache
if (!drawResult.Ok)
{
throw new Exception(drawResult.Message);
}
_user = drawResult.User;
var listResult = new List (drawResult.Items.Count);
listResult.AddRange(drawResult.Items.Select(it => new Item
{
Type = it.Type switch
{
0 => ItemType.Hero,
1 => ItemType.Prop,
_ => ItemType.Other
},
Name = it.Name,
Count = it.Count,
Level = it.Level
}));

onDrawCard.Invoke(listResult);
}
catch (Exception e)
{
onError.Invoke(e.Message, 3);
}
}

6.上傳和部署云函數(shù),切換到遠(yuǎn)程調(diào)用模式下測(cè)試云函數(shù)的調(diào)用

當(dāng)全部驗(yàn)證完代碼邏輯后,請(qǐng)先切換成遠(yuǎn)程模式上傳云函數(shù)后再進(jìn)行驗(yàn)證遠(yuǎn)程調(diào)用

6.1上傳云函數(shù)

所以,我們現(xiàn)在就可以點(diǎn)擊 Func Stateless Tool 面板的「上傳云函數(shù)」的按鈕,來(lái)等待云函數(shù)的構(gòu)建和部署。

當(dāng)云函數(shù)構(gòu)建完成后,工具會(huì)自動(dòng)將云函數(shù)部署到 Func Stateless。 可以通過(guò)點(diǎn)擊工具中的時(shí)間戳,快速跳轉(zhuǎn)到網(wǎng)頁(yè)控制臺(tái)查看云函數(shù)部署情況。

可以看到已經(jīng)自動(dòng)將云函數(shù) loginservice 和 actionservice 部署到 Func Stateless 了。

點(diǎn)擊云函數(shù)代碼緩存路徑下的文件,會(huì)自動(dòng)為你下載 zip 文件的。

可以點(diǎn)擊查看下,會(huì)自動(dòng)將我們配置的云函數(shù)路徑(Scripts/CloudService)下的腳本都緩存到 zip 壓縮包中的。

6.2切換成遠(yuǎn)程調(diào)用模式

云函數(shù)的調(diào)用模式分為本地調(diào)用和遠(yuǎn)程調(diào)用,之前我們已經(jīng)運(yùn)行項(xiàng)目測(cè)試過(guò),都屬于在本地調(diào)用云函數(shù)。

現(xiàn)在,讓我們?cè)?Func Stateless Tool 工具中,將云函數(shù)的調(diào)用模式由「本地調(diào)用」切換到「遠(yuǎn)程調(diào)用」主要的目的還是防止客戶端包體可能會(huì)被破解,所以權(quán)威性邏輯不能在客戶端執(zhí)行,而必須在服務(wù)端上執(zhí)行。

遠(yuǎn)程調(diào)用:它指的是自動(dòng)將本地函數(shù)調(diào)用請(qǐng)求改寫成 HTTP 等網(wǎng)絡(luò)服務(wù)請(qǐng)求。

切換到遠(yuǎn)程調(diào)用模式后,云函數(shù)代碼發(fā)生了變化。打開(kāi)腳本可以看到,云函數(shù)中的代碼由原來(lái)的業(yè)務(wù)邏輯變成了遠(yuǎn)程調(diào)用的代碼。如下圖所示:

LoginService.cs腳本中的登錄云函數(shù) Login:

[CloudService]
public class LoginService
{
[CloudFunc]
public async Task Login( string username)
{
var jwtToken = await AuthTokenManager.GetAccessToken(Settings.AppID);
FuncContext.SetAppIdSecret(Settings.AppID, Settings.AppSecret);
FuncContext.SetToken(jwtToken);
var json = "{" + $"\"username\":{JsonConvert.SerializeObject(username)}" + "}";
var jsonResult = await HttpClient.Call($"https://{LoginServiceq3k9FwJi5A.Domain}/release/c1ac2450-730c-4f5a-a15a-7a314c6b9a9e/loginservice", "login", json);
try
{
return JsonConvert.DeserializeObject (jsonResult);
}
catch (Exception ex)
{
Debug.LogException(ex);
throw new ExecuteException(jsonResult);
}
}
}

ActionService.cs腳本中的抽卡云函數(shù) Draw:

[CloudFunc]
public async Task Draw( string saveId, int count)
{
var jwtToken = await AuthTokenManager.GetAccessToken(Settings.AppID);
FuncContext.SetAppIdSecret(Settings.AppID, Settings.AppSecret);
FuncContext.SetToken(jwtToken);
var json = "{" + $"\"saveId\":{JsonConvert.SerializeObject(saveId)},\"count\":{JsonConvert.SerializeObject(count)}" + "}";
var jsonResult = await HttpClient.Call($"https://{ActionServiceq3k9FwJi5A.Domain}/release/c1ac2450-730c-4f5a-a15a-7a314c6b9a9e/actionservice", "draw", json);
try
{
return JsonConvert.DeserializeObject (jsonResult);
}
catch (Exception ex)
{
Debug.LogException(ex);
throw new ExecuteException(jsonResult);
}
}

6.3 查看調(diào)用的日志信息

切換成【遠(yuǎn)程調(diào)用】模式后,再次運(yùn)行游戲項(xiàng)目。

然后回到 UOS 網(wǎng)頁(yè)端的云函數(shù)頁(yè)面,可以查看云函數(shù)的調(diào)用日志,可以看到項(xiàng)目已經(jīng)成功調(diào)用了上傳的云函數(shù)。

6.4重置云函數(shù)

Func Stateless Tool 工具還給我們提供了重置云函數(shù)功能。如果點(diǎn)擊【重置云函數(shù)】按鈕后,會(huì)將云函數(shù)回退到上一個(gè)版本的云函數(shù)。然后當(dāng)我們修改云函數(shù)的代碼后,可以再次上傳新的云函數(shù)。

本期教程我們先講解到這里,大家趕快下載 Demo 項(xiàng)目試試吧!我們下一期再見(jiàn)哦!

Unity Online Services (UOS) 是一個(gè)專為游戲開(kāi)發(fā)者設(shè)計(jì)的一站式游戲云服務(wù)平臺(tái),提供覆蓋游戲全生命周期的開(kāi)發(fā)、運(yùn)營(yíng)和推廣支持。

了解更多 UOS 相關(guān)信息:

官網(wǎng):https://uos.unity.cn

技術(shù)交流 QQ 群:823878269

公眾號(hào):UOS 游戲云服務(wù)

Unity 官方微信

第一時(shí)間了解Unity引擎動(dòng)向,學(xué)習(xí)進(jìn)階開(kāi)發(fā)技能

每一個(gè)“點(diǎn)贊”、“在看”,都是我們前進(jìn)的動(dòng)力

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關(guān)推薦
熱點(diǎn)推薦
突然就想通了!人早晚都得死,活著就應(yīng)該這么過(guò)

突然就想通了!人早晚都得死,活著就應(yīng)該這么過(guò)

娛樂(lè)洞察點(diǎn)點(diǎn)
2025-04-06 11:15:11
知名軟件正式停運(yùn)!有人昨天還在用,網(wǎng)友:要跟很多人失聯(lián)了

知名軟件正式停運(yùn)!有人昨天還在用,網(wǎng)友:要跟很多人失聯(lián)了

瀟湘晨報(bào)
2025-05-07 09:40:15
竇驍&何超蓮,知情網(wǎng)友:沒(méi)登記,分居已久,就等合適時(shí)間宣布了

竇驍&何超蓮,知情網(wǎng)友:沒(méi)登記,分居已久,就等合適時(shí)間宣布了

清游說(shuō)娛
2025-04-24 09:43:39
10輪坐穿板凳!昔日國(guó)足希望之星慘遭塞蒂恩無(wú)視,淪為中超多余人

10輪坐穿板凳!昔日國(guó)足希望之星慘遭塞蒂恩無(wú)視,淪為中超多余人

零度眼看球
2025-05-08 09:10:48
總決賽0勝36負(fù)?廣廈4人已經(jīng)輸不起了:他們何時(shí)能等來(lái)首勝?

總決賽0勝36負(fù)?廣廈4人已經(jīng)輸不起了:他們何時(shí)能等來(lái)首勝?

籃球快餐車
2025-05-08 08:07:31
真是度日如年!網(wǎng)友描述嫖娼被拘留的經(jīng)歷

真是度日如年!網(wǎng)友描述嫖娼被拘留的經(jīng)歷

煙語(yǔ)法明
2025-05-04 23:41:09
唐斯談大橋:我們不僅有年度關(guān)鍵先生 還有關(guān)鍵時(shí)刻最佳防守球員

唐斯談大橋:我們不僅有年度關(guān)鍵先生 還有關(guān)鍵時(shí)刻最佳防守球員

北青網(wǎng)-北京青年報(bào)
2025-05-08 20:31:03
尼克斯完成本賽季季后賽第5次20+分翻盤,創(chuàng)近28年NBA最高紀(jì)錄

尼克斯完成本賽季季后賽第5次20+分翻盤,創(chuàng)近28年NBA最高紀(jì)錄

雷速體育
2025-05-08 09:44:20
奧沙利文隔空叫陣!丨36歲魔咒成導(dǎo)火索丨中英斯諾克世紀(jì)賭約

奧沙利文隔空叫陣!丨36歲魔咒成導(dǎo)火索丨中英斯諾克世紀(jì)賭約

生活新鮮市
2025-05-08 15:15:05
黃瑋接替周繼紅出任國(guó)家體育總局游泳運(yùn)動(dòng)管理中心主任

黃瑋接替周繼紅出任國(guó)家體育總局游泳運(yùn)動(dòng)管理中心主任

澎湃新聞
2025-05-08 14:08:27
91萬(wàn)大陸游客被罵“窮鬼”,香港民眾直言:沒(méi)酒店訂房禁止入境!

91萬(wàn)大陸游客被罵“窮鬼”,香港民眾直言:沒(méi)酒店訂房禁止入境!

小毅說(shuō)事
2025-05-07 19:44:41
第一次去閨蜜家上廁所,剛進(jìn)去我臉通紅,這也太會(huì)享受了,現(xiàn)在的年輕人都這樣?

第一次去閨蜜家上廁所,剛進(jìn)去我臉通紅,這也太會(huì)享受了,現(xiàn)在的年輕人都這樣?

裝小宅
2025-05-08 08:52:49
黃仁勛最新涉華表態(tài)

黃仁勛最新涉華表態(tài)

環(huán)球時(shí)報(bào)國(guó)際
2025-05-08 00:18:10
西方突然意識(shí)到:只要中國(guó)下狠手,美國(guó)可能連仗都沒(méi)法打下去

西方突然意識(shí)到:只要中國(guó)下狠手,美國(guó)可能連仗都沒(méi)法打下去

小lu侃侃而談
2025-04-15 20:16:36
醫(yī)生痛斥:老年癡呆最早信號(hào),不是忘事!而是頻繁出現(xiàn)這5異常!

醫(yī)生痛斥:老年癡呆最早信號(hào),不是忘事!而是頻繁出現(xiàn)這5異常!

窗外的光
2025-05-07 09:43:14
29國(guó)領(lǐng)導(dǎo)人齊聚莫斯科,普京給中國(guó)統(tǒng)一大業(yè),備好一份意外驚喜?

29國(guó)領(lǐng)導(dǎo)人齊聚莫斯科,普京給中國(guó)統(tǒng)一大業(yè),備好一份意外驚喜?

蘭妮搞笑分享
2025-05-08 15:06:42
官宣喜訊,35歲丁寧發(fā)聲,困擾大事解決,或回歸國(guó)乒任教

官宣喜訊,35歲丁寧發(fā)聲,困擾大事解決,或回歸國(guó)乒任教

樂(lè)聊球
2025-05-08 16:07:26
女護(hù)士遭領(lǐng)導(dǎo)多次侵犯,哀求:你年紀(jì)太大,別再找我了好不好

女護(hù)士遭領(lǐng)導(dǎo)多次侵犯,哀求:你年紀(jì)太大,別再找我了好不好

趙主任微小說(shuō)
2025-04-17 17:00:49
鴻蒙電腦問(wèn)世:歷時(shí)五年布局,年底支持超2000個(gè)應(yīng)用

鴻蒙電腦問(wèn)世:歷時(shí)五年布局,年底支持超2000個(gè)應(yīng)用

PChome電腦之家
2025-05-08 10:16:24
我在印度生活幾個(gè)月,說(shuō)真的,印度是我去過(guò)最讓人破防的國(guó)家。

我在印度生活幾個(gè)月,說(shuō)真的,印度是我去過(guò)最讓人破防的國(guó)家。

侃侃兒談
2025-05-03 08:41:37
2025-05-08 21:15:00
Unity incentive-icons
Unity
Unity中國(guó)官方帳戶
2300文章數(shù) 6718關(guān)注度
往期回顧 全部

科技要聞

迎戰(zhàn)618,靠AI出圈后,快手有了新打法

頭條要聞

法方確認(rèn)印軍"陣風(fēng)"戰(zhàn)機(jī)被擊落 巴總理透露空戰(zhàn)細(xì)節(jié)

頭條要聞

法方確認(rèn)印軍"陣風(fēng)"戰(zhàn)機(jī)被擊落 巴總理透露空戰(zhàn)細(xì)節(jié)

體育要聞

面對(duì)一群天賦怪,阿森納只能接受失敗

娛樂(lè)要聞

劉畊宏老婆補(bǔ)刀 清場(chǎng)風(fēng)波口碑翻車!

財(cái)經(jīng)要聞

57政策解讀:力度空前的系統(tǒng)性穩(wěn)增長(zhǎng)舉措

汽車要聞

昨天李想點(diǎn)評(píng)了AI 今天我讓AI點(diǎn)評(píng)了理想

態(tài)度原創(chuàng)

數(shù)碼
手機(jī)
本地
房產(chǎn)
公開(kāi)課

數(shù)碼要聞

音箱智商史詩(shī)升級(jí) AI大模型滲透率已超20%

手機(jī)要聞

三星S25 Edge將于5月中旬發(fā)布 5.8mm機(jī)身厚度重量?jī)H163g

本地新聞

非遺里的河南|汴梁鳶舞千年韻!宋室風(fēng)箏藏多少絕活

房產(chǎn)要聞

廣州樓市全線飄紅!二手增長(zhǎng)20%,一手暴漲244.7%!

公開(kāi)課

李玫瑾:為什么性格比能力更重要?

無(wú)障礙瀏覽 進(jìn)入關(guān)懷版 主站蜘蛛池模板: 双鸭山市| 南和县| 中阳县| 达州市| 南城县| 湘潭市| 徐水县| 科技| 临汾市| 大连市| 万源市| 柘城县| 古丈县| 辽宁省| 漾濞| 望都县| 科尔| 安远县| 嘉兴市| 扎赉特旗| 东城区| 体育| 陆川县| 铜鼓县| 磴口县| 天水市| 绵阳市| 福鼎市| 宁蒗| 江安县| 衡山县| 龙胜| 色达县| 六安市| 成安县| 梓潼县| 建平县| 盐亭县| 晋州市| 军事| 金华市|