開放雲端資料儲存

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

除了從 Studio 或即時遊戲中的引擎 API (DataStoreService) 存取資料儲存外,您還可以使用開放雲端 API 從外部腳本和其他工具存取 標準有序資料儲存

開放雲端對您的資料儲存的存取解鎖了許多潛在的使用案例,包括:

  • 一個客戶支援入口網站,讓您的團隊直接處理支援請求,例如修改用戶的庫存或發放退款
  • 可以在外部網站上顯示的全球排行榜
  • 使用腳本進行的架構更新,這些腳本從當前資料儲存中讀取條目,將其映射到新架構,並將條目寫回新的資料儲存

本頁的範例展示了如何使用 Node.js 和 Python 建立 用戶庫存支援入口網站外部排行榜,但您可以使用任何您喜歡的語言;開放雲端 API 支援任何可以發送 HTTP 請求的程式語言。

與引擎 API 的差異

雖然開放雲端 API 存取相同的底層資料儲存,並且與使用 DataStoreService 類似,但有幾個關鍵差異:

  • 宇宙 ID:與引擎 API 不同,開放雲端 API 是無狀態的,可以來自任何地方,因此您必須始終提供 宇宙 ID,即您遊戲的唯一識別碼。

  • 創建和更新的單獨權限:引擎 API 在您調用 DataStore:SetAsync() 時,如果條目不存在則會創建新條目,但開放雲端的創建和更新條目的方法是分開的。在某些情況下,單獨的權限可能更安全且更靈活。例如,您可能希望您的客戶支援工具只能編輯現有用戶的資料,而不是創建新的資料。

  • 資料序列化:所有開放雲端端點都要求您在發送資料之前進行序列化。序列化是將物件轉換為字串。反序列化則是相反的,將字串轉換為物件。引擎 API 自動序列化和反序列化條目內容,但對於開放雲端,您必須自己生成或解析條目到 JSON。

非有限數字

通過引擎 API 寫入的資料儲存條目可以包含非有限的 Luau 數字。由於 JSON 無法表示這些數字,開放雲端的回應會將它們替換為返回條目值中的標記 JSON 物件:

  • 正無限大 (inf): {"m": null, "t": "numeric", "v": "inf"}
  • 負無限大 (-inf): {"m": null, "t": "numeric", "v": "-inf"}
  • NaN (nan): {"m": null, "t": "numeric", "v": "nan"}

權限

資料儲存通常存儲敏感資訊,例如用戶資料和虛擬貨幣。為了維護安全性,每個開放雲端方法都有相應的權限,稱為 範圍,您必須將其添加到您的 API 金鑰中,例如 列出資料儲存 方法的 universe-datastores.control:list 範圍。如果您未添加所需的權限,您的 API 調用將返回錯誤。請參閱參考文檔以獲取每個端點所需的範圍。

有關管理權限的更多資訊,請參見 管理 API 金鑰

用戶庫存支援入口網站

此範例使用名為 Inventory 的資料儲存,並為每個條目定義架構 "userId": {"currency": number, "weapon": string, "level": number}。鍵是 userId

所需範圍

在為此範例 創建 API 金鑰 時,將以下範圍添加到您的金鑰中:

  • universe-datastores.objects:list
  • universe-datastores.objects:read
  • universe-datastores.objects:update

可選地,僅為 Inventory 資料儲存添加權限,設置 IP 地址限制,並設置到期日期。

為用戶庫存支援入口網站添加腳本

在創建具有範例應用所需權限的 API 金鑰後,您可以創建一個腳本來向端點發送請求。這些腳本獲取資料儲存中的前 10 個條目,將每個條目的 currency 值增加 10,然後更新每個條目。對於較大的資料儲存,您需要使用 maxPageSizepageToken 查詢參數處理 分頁

incrementCurrency.js
const apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error('API_KEY 環境變數未設置。');
}
const apiHeaderKey = 'x-api-key';
const universeId = '';
const dataStoreId = 'Inventory';
const baseUrl = 'https://apis.roblox.com/cloud/v2/';
async function listEntries(universe, dataStore) {
const listPath = `universes/${universe}/data-stores/${dataStore}/entries`;
const url = baseUrl + listPath;
const response = await fetch(url, {
headers: { [apiHeaderKey]: apiKey }
});
return response.json();
}
async function getEntry(path) {
const url = baseUrl + path;
const response = await fetch(url, {
headers: { [apiHeaderKey]: apiKey }
});
return response.json();
}
async function updateEntry(path, payload) {
const url = baseUrl + path;
const response = await fetch(url, {
method: 'PATCH',
headers: {
[apiHeaderKey]: apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload) // 主體必須是字串
});
return response;
}
(async () => {
try {
const entries = await listEntries(universeId, dataStoreId);
for (const entry of entries.dataStoreEntries) {
const path = entry.path;
console.log(`\n處理條目: ${path}`);
const currentData = await getEntry(path);
currentData.value.currency += 10;
const payload = { value: currentData.value };
const updateResponse = await updateEntry(path, payload);
console.log(`狀態: ${updateResponse.status}`);
console.log(`回應: ${await updateResponse.text()}`);
}
} catch (error) {
console.error('執行期間發生錯誤:', error);
}
})();

要測試,設置 API_KEY 環境變數,安裝依賴,然後運行腳本:

export API_KEY=<your_key>
node incrementCurrency.js

外部持久排行榜

此範例為演示目的創建了一個預定義的用戶列表,但要在實際遊戲中有用,您需要一個實際的用戶資料儲存。

所需範圍

在為此範例 創建 API 金鑰 時,將以下範圍添加到您的金鑰中:

  • universe.ordered-data-store.scope.entry:read
  • universe.ordered-data-store.scope.entry:write

為排行榜添加腳本

在創建具有範例應用所需權限的 API 金鑰後,您可以創建一個腳本來向端點發送請求。這些腳本將一些隨機數的樣本條目添加到有序資料儲存中,然後從最高值檢索它們。對於較大的資料儲存,您需要使用 maxPageSizepageToken 查詢參數處理 分頁

leaderboard.js
const apiKey = process.env.API_KEY;
const apiHeaderKey = 'x-api-key';
const universeId = '';
const orderedDataStoreId = 'PlayerScores';
const scopeId = 'global';
const baseUrl = 'https://apis.roblox.com/cloud/v2/';
async function createOrderedEntry(universe, orderedDataStore, entryId, payload) {
const createPath = `universes/${universe}/ordered-data-stores/${orderedDataStore}/scopes/${scopeId}/entries`;
const url = new URL(baseUrl + createPath);
url.searchParams.append('id', entryId);
const response = await fetch(url, {
method: 'POST',
headers: {
[apiHeaderKey]: apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API 錯誤 (${response.status}): ${errorText}`);
}
return response.text();
}
async function listOrderedEntries(universe, orderedDataStore) {
const listPath = `universes/${universe}/ordered-data-stores/${orderedDataStore}/scopes/${scopeId}/entries`;
const url = new URL(baseUrl + listPath);
url.searchParams.append('orderBy', 'value desc');
return fetch(url, {
headers: {
[apiHeaderKey]: apiKey,
},
});
}
async function main() {
if (!apiKey) {
console.error('錯誤: API_KEY 環境變數未設置。');
process.exit(1);
}
const entryNames = ['Ragdoll', 'Balinese', 'Tabby', 'Siamese'];
console.log('創建樣本資料...');
for (const name of entryNames) {
try {
const randomValue = Math.floor(Math.random() * 50) + 1;
const payload = { value: randomValue };
const responseText = await createOrderedEntry(universeId, orderedDataStoreId, name, payload);
console.log(responseText);
} catch (error) {
console.error(`無法為 "${name}" 創建條目: ${error.message}`);
}
}
console.log('\n獲取排序後的條目列表...');
try {
const playerScoresResponse = await listOrderedEntries(universeId, orderedDataStoreId);
console.log(playerScoresResponse.status);
const responseText = await playerScoresResponse.text();
console.log(responseText);
} catch (error) {
console.error(`無法列出條目: ${error.message}`);
}
}
main();

要測試,設置 API_KEY 環境變數,安裝依賴,然後運行腳本:

export API_KEY=<your_key>
node leaderboard.js
©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。