Oprócz uzyskiwania dostępu do magazynów danych z API silnika w Studio lub w grach na żywo (DataStoreService), możesz używać interfejsów API Open Cloud do uzyskiwania dostępu do standardowych i uporządkowanych magazynów danych z zewnętrznych skryptów i innych narzędzi.
Dostęp do magazynów danych w chmurze otwiera wiele potencjalnych zastosowań, w tym:
- Portal wsparcia klienta, który pozwala Twojemu zespołowi bezpośrednio obsługiwać zgłoszenia wsparcia, takie jak modyfikowanie inwentarzy użytkowników lub wydawanie zwrotów
- Globalne rankingi, które możesz wyświetlać na zewnętrznej stronie internetowej
- Aktualizacje schematu za pomocą skryptów, które odczytują wpisy z bieżącego magazynu danych, mapują je do nowego schematu i zapisują wpisy z powrotem do nowego magazynu danych
Przykłady na tej stronie pokazują, jak zbudować portal wsparcia inwentarza użytkownika oraz zewnętrzny ranking za pomocą Node.js i Pythona, ale użyj dowolnego języka, który preferujesz; interfejsy API Open Cloud obsługują każdy język programowania, który może wysyłać żądanie HTTP.
Różnice w porównaniu do API silnika
Chociaż interfejsy API Open Cloud uzyskują dostęp do tych samych podstawowych magazynów danych i są podobne do pracy z DataStoreService, istnieje kilka kluczowych różnic:
ID uniwersum: W przeciwieństwie do API silnika, interfejsy API Open Cloud są bezstanowe i mogą pochodzić z dowolnego miejsca, więc zawsze musisz podać ID uniwersum, unikalny identyfikator Twojej gry.
Oddzielne uprawnienia do tworzenia i aktualizacji: API silnika tworzy nowe wpisy, jeśli nie istnieją, gdy wywołujesz DataStore:SetAsync(), ale metody Open Cloud do tworzenia i aktualizacji wpisów są oddzielne. Oddzielne uprawnienia mogą być bezpieczniejsze i bardziej elastyczne w niektórych sytuacjach. Na przykład, możesz chcieć, aby Twoje narzędzie wsparcia klienta mogło tylko edytować profil istniejącego użytkownika, a nie tworzyć nowego.
Serializacja danych: Wszystkie punkty końcowe Open Cloud wymagają, abyś serializował dane przed ich wysłaniem. Serializacja oznacza konwersję obiektu na ciąg. Deserializacja to przeciwieństwo, konwersja ciągu na obiekt. API silnika automatycznie serializuje i deserializuje zawartość wpisów, ale w przypadku Open Cloud musisz samodzielnie generować lub analizować swój wpis do i z JSON.
Liczby nieskończone
Wpisy magazynów danych zapisane przez API silnika mogą zawierać nieskończone liczby Luau. Ponieważ JSON nie może reprezentować tych liczb, odpowiedzi Open Cloud zastępują je oznaczonymi obiektami JSON w zwracanej wartości wpisu:
- Dodatnia nieskończoność (inf): {"m": null, "t": "numeric", "v": "inf"}
- Ujemna nieskończoność (-inf): {"m": null, "t": "numeric", "v": "-inf"}
- NaN (nan): {"m": null, "t": "numeric", "v": "nan"}
Uprawnienia
Magazyny danych często przechowują wrażliwe informacje, takie jak profile użytkowników i waluta wirtualna. Aby zachować bezpieczeństwo, każda metoda Open Cloud ma odpowiadające uprawnienia, zwane zakresami, które musisz dodać do swojego klucza API, takie jak zakres universe-datastores.control:list dla metody List Data Stores. Jeśli nie dodasz wymaganych uprawnień, Twoje wywołanie API zwróci błąd. Zobacz dokumentację referencyjną, aby uzyskać wymagane zakresy dla każdego punktu końcowego.
Aby uzyskać więcej informacji na temat zarządzania uprawnieniami, zobacz Zarządzaj kluczami API.
Portal wsparcia inwentarza użytkownika
Ten przykład używa magazynu danych o nazwie Inventory oraz schematu dla każdego wpisu "userId": {"currency": number, "weapon": string, "level": number}. Klucz to userId.
Wymagane zakresy
Podczas tworzenia klucza API dla tego przykładu, dodaj następujące zakresy do swojego klucza:
- universe-datastores.objects:list
- universe-datastores.objects:read
- universe-datastores.objects:update
Opcjonalnie, dodaj uprawnienia tylko dla magazynu danych Inventory, ustaw ograniczenie adresu IP i datę wygaśnięcia.
Dodaj skrypty do portalu wsparcia inwentarza użytkownika
Po utworzeniu klucza API z wymaganymi uprawnieniami dla przykładowej aplikacji, możesz stworzyć skrypt do wysyłania żądań do punktów końcowych. Te skrypty pobierają pierwsze 10 wpisów w magazynie danych, zwiększają wartość currency dla każdego o 10, a następnie aktualizują każdy wpis. Dla większego magazynu danych musiałbyś zająć się stronicowaniem za pomocą parametrów zapytania maxPageSize i pageToken.
const apiKey = process.env.API_KEY;
if (!apiKey) {
throw new Error('Zmienna środowiskowa API_KEY nie jest ustawiona.');
}
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) // Ciało musi być ciągiem
});
return response;
}
(async () => {
try {
const entries = await listEntries(universeId, dataStoreId);
for (const entry of entries.dataStoreEntries) {
const path = entry.path;
console.log(`\nPrzetwarzanie wpisu: ${path}`);
const currentData = await getEntry(path);
currentData.value.currency += 10;
const payload = { value: currentData.value };
const updateResponse = await updateEntry(path, payload);
console.log(`Status: ${updateResponse.status}`);
console.log(`Odpowiedź: ${await updateResponse.text()}`);
}
} catch (error) {
console.error('Wystąpił błąd podczas wykonywania:', error);
}
})();Aby przetestować, ustaw zmienną środowiskową API_KEY, zainstaluj zależności i uruchom skrypt:
export API_KEY=<your_key>
node incrementCurrency.jsZewnętrzny trwały ranking
Ten przykład tworzy wstępnie zdefiniowaną listę użytkowników w celach demonstracyjnych, ale aby był użyteczny w prawdziwej grze, potrzebowałbyś rzeczywistego magazynu danych użytkowników.
Wymagane zakresy
Podczas tworzenia klucza API dla tego przykładu, dodaj następujące zakresy do swojego klucza:
- universe.ordered-data-store.scope.entry:read
- universe.ordered-data-store.scope.entry:write
Dodaj skrypty do rankingu
Po utworzeniu klucza API z wymaganymi uprawnieniami dla przykładowej aplikacji, możesz stworzyć skrypt do wysyłania żądań do punktów końcowych. Te skrypty dodają kilka przykładowych wpisów do uporządkowanego magazynu danych z losowymi liczbami, a następnie pobierają je od najwyższej do najniższej wartości. Dla większego magazynu danych musiałbyś zająć się stronicowaniem za pomocą parametrów zapytania maxPageSize i pageToken.
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(`Błąd 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('Błąd: Zmienna środowiskowa API_KEY nie jest ustawiona.');
process.exit(1);
}
const entryNames = ['Ragdoll', 'Balinese', 'Tabby', 'Siamese'];
console.log('Tworzenie przykładowych danych...');
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(`Nie udało się utworzyć wpisu dla "${name}": ${error.message}`);
}
}
console.log('\nPobieranie posortowanej listy wpisów...');
try {
const playerScoresResponse = await listOrderedEntries(universeId, orderedDataStoreId);
console.log(playerScoresResponse.status);
const responseText = await playerScoresResponse.text();
console.log(responseText);
} catch (error) {
console.error(`Nie udało się wylistować wpisów: ${error.message}`);
}
}
main();Aby przetestować, ustaw zmienną środowiskową API_KEY, zainstaluj zależności i uruchom skrypt:
export API_KEY=<your_key>
node leaderboard.js