除了通过Studio或实时游戏中的引擎API (DataStoreService) 访问数据存储之外,您还可以使用开放云API访问来自外部脚本和其他工具的标准和有序数据存储。
开放云对您的数据存储的访问解锁了许多潜在的用例,包括:
- 一个客户支持门户,让您的团队直接处理支持请求,例如修改用户的库存或发放退款
- 可以在外部网站上显示的全球排行榜
- 使用脚本进行的架构更新,脚本从当前数据存储读取条目,将其映射到新的架构,并将条目写回新的数据存储
本页面的示例演示如何使用Node.js和Python构建一个用户库存支持门户和一个外部排行榜,但您可以使用任何您喜欢的语言;开放云API支持任何能够发送HTTP请求的编程语言。
与引擎API的区别
尽管开放云API访问相同的底层数据存储,并且与使用 DataStoreService 类似,但有一些关键的区别:
宇宙ID:与引擎API不同,开放云API是无状态的,可以来自任何地方,因此您必须始终提供宇宙ID,即您游戏的唯一标识符。
创建和更新的单独权限:引擎API在您调用 DataStore:SetAsync() 时,如果条目不存在,它会创建新的条目,但开放云方法用于创建和更新条目是分开的。单独的权限在某些情况下可能更安全、更灵活。例如,您可能希望您的客户支持工具仅能编辑现有用户的个人资料,而不能创建新的。
数据序列化:所有开放云端点都需要您在发送数据之前序列化数据。序列化是指将对象转换为字符串。反序列化是相反的过程,即将字符串转换为对象。引擎API会自动序列化和反序列化条目内容,但对于开放云,您必须自行生成或解析条目到JSON。
权限
数据存储通常存储敏感信息,例如用户个人资料和虚拟货币。为了维护安全性,每个开放云方法都有相应的权限,称为范围,您必须将这些范围添加到您的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,然后更新每个条目。对于更大的数据存储,您需要使用 maxPageSize 和 pageToken 查询参数来处理分页。
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密钥后,您可以创建一个脚本以对端点进行请求。这些脚本将一些样本条目添加到有序数据存储中,使用随机数,然后按值从高到低检索它们。对于更大的数据存储,您需要使用 maxPageSize 和 pageToken 查询参数来处理分页。
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