오픈 클라우드 데이터 저장소

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

엔진 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'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.