云存档开发指南
阅读此文档前请先阅读云存档功能介绍,了解云存档的核心概念及功能。
权限说明
- Unity
- Android
- iOS
- UE4
该模块需要如下权限:
权限 | 使用目的 | 权限申请时机 |
---|---|---|
网络权限 | 用于访问网络数据 | 用户首次使用该功能时会申请权限 |
该模块将在应用中添加如下权限:
<uses-permission android:name="android.permission.INTERNET" />
集成前准备
接入云存档的前提是:
绑定域名,包括 API 域名和文件域名。
云存档需要依赖于内建账户,因此需要通过开发者中心开通 TDS 内建账户。游戏存档会绑定在 TDSUser 下,所以请首先参考内建账户系统集成文档完成 SDK 获取 、SDK 初始化以及登录功能的接入。
创建存档
SDK 会自动获取当前登录玩家(TDSUser)信息,关联到存档上。 因此,用户已登录时才能创建存档。
- Unity
- Android
- iOS
var gameSave = new TapGameSave
{
Name = "internal name",
Summary = "description",
ModifiedAt = DateTime.Now.ToLocalTime(),
PlayedTime = 60000L, // ms
ProgressValue = 100,
CoverFilePath = image_local_path, // jpg/png
GameFilePath = dll_local_path
};
await gameSave.Save();
TapGameSave snapshot = new TapGameSave();
snapshot.setName("internal name");
snapshot.setSummary("description");
snapshot.setPlayedTime(60000); // ms
snapshot.setProgressValue(100);
snapshot.setCover(image_local_path); // jpg/png
snapshot.setGameFile(dll_local_path);
snapshot.setModifiedAt(new Date());
snapshot.saveInBackground().subscribe(new Observer<TapGameSave>() {
@Override
public void onSubscribe(@NotNull Disposable d) {}
@Override
public void onNext(@NotNull TapGameSave gameSave) {
System.out.println("存档保存成功:" + gameSave.toJSONString());
}
@Override
public void onError(@NotNull Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {}
});
TapGameSave *gameSave = [TapGameSave new];
gameSave.name = @"internal name";
gameSave.summary = @"description";
gameSave.modifiedAt = [NSDate date];
gameSave.playedTime = 60000; // ms
gameSave.progressValue = 100;
[gameSave setCoverWithLocalPath:@"image_local_path" error:&error]; // jpg/png
[gameSave setGameFileWithLocalPath:@"dll_local_path" error:&error];
[gameSave saveInBackgroundWithBlock:^(BOOL succeeded, NSError *_Nullable error) {
if (succeeded) {
NSLog(@"保存成功。objectId:%@", gameSave.objectId);
} else {
// 异常处理
}
}];
上面的例子中,存档元信息字段的含义请参考云存档保存的游戏元数据。 保存时,SDK 会限制仅当前玩家可以读写存档本身及关联的存档文件、封面文件。
查询用户存档
最常见的场景是获取当前玩家的所有存档:
- Unity
- Android
- iOS
var collection = await TapGameSave.GetCurrentUserGameSaves();
foreach(var gameSave in collection){
var summary = gameSave.Summary;
var modifiedAt = gameSave.ModifiedAt;
var playedTime = gameSave.PlayedTime;
var progressValue = gameSave.ProgressValue;
var coverFile = gameSave.Cover;
var gameFile = gameSave.GameFile;
var gameFileUrl = gameFile.Url;
}
gameFile.Url
是保存在云端的存档文件的下载地址,通过这个 URL 下载的文件格式和上传时的格式保持一致。
TapGameSave.getCurrentUserGameSaves()
.subscribe(new Observer<List<TapGameSave>>() {
@Override
public void onSubscribe(@NotNull Disposable d) {}
@Override
public void onNext(@NotNull List<TapGameSave> tapGameSaves) {
for (TapGameSave gameSave : tapGameSaves) {
String summary = gameSave.getSummary();
Date modifiedAt = gameSave.getModifiedAt();
double playedTime = gameSave.getPlayedTime();
int progressValue = gameSave.getProgressValue();
LCFile cover = gameSave.getCover();
LCFile gameFile = gameSave.getGameFile();
}
}
@Override
public void onError(@NotNull Throwable e) {
e.printStackTrace();
}
@Override
public void onComplete() {}
});
LCQuery *query = [TapGameSave queryWithCurrentUser];
[query findObjectsInBackgroundWithBlock:^(NSArray *_Nullable gameSaves, NSError *_Nullable error) {
if (error) {
NSLog(@"test fail because %@", error);
} else {
for (TapGameSave *gameSave in gameSaves) {
NSString *summary = gameSave.summary;
NSDate *modifiedAt = gameSave.modifiedAt;
double playedTime = gameSave.playedTime;
int progressValue = gameSave.progressValue;
LCFile* cover = gameSave.cover;
LCFile* gameFile = gameSave.gameFile;
}
}
}];
当然也可以查询满足特定条件的存档,比如查询当前玩家游戏进度超过第 3 关的存档:
- Unity
- Android
- iOS
TDSUser user = await TDSUser.GetCurrent();
LCQuery<TapGameSave> gameSaveQuery = TapGameSave.GetQueryWithUser(user);
gameSaveQuery.WhereGreaterThan("progressValue", 3);
var collections = await gameSaveQuery.Find();
查询条件的构造请参考数据存储指南查询章节的说明。
LCQuery<TapGameSave> gameSaveQuery = TapGameSave.getQueryWithUser();
gameSaveQuery.whereGreaterThan("progressValue", 3);
gameSaveQuery.findInBackground().subscribe(new Observer<List<LCObject>>() {
public void onSubscribe(Disposable disposable) {}
public void onNext(List<TapGameSave> gamesaves) {
// gamesaves 是包含满足条件的云存档对象的数组
}
public void onError(Throwable throwable) {}
public void onComplete() {}
});
查询条件的构造请参考数据存储指南查询章节的说明。
LCQuery *query = [TapGameSave queryWithCurrentUser];
[query whereKey:@"progressValue" greaterThan:@3];
[query findObjectsInBackgroundWithBlock:^(NSArray *_Nullable gameSaves, NSError *_Nullable error) {
// 略
}];
注意,如果之前整个游戏的任意玩家从未保存过存档,那么查询存档会报错 Class or object doesn't exists.
这是因为云存档在底层数据库层面的表(Class)只在保存第一个存档时才会创建。
删除用户存档
玩家只能删除自己的存档。
删除存档:
- Unity
- Android
- iOS
await gameSave.Delete();
gameSave.deleteInBackground().subscribe(new Observer<LCNull>() {
@Override
public void onSubscribe(@NonNull Disposable d) {}
@Override
public void onNext(LCNull response) {
// Deleted.
}
@Override
public void onError(@NonNull Throwable e) {
System.out.println("Failed to delete:" + e.getMessage());
}
@Override
public void onComplete() {}
});
[gameSave deleteInBackground];
删除存档时云端会自动删除关联的封面文件和存档原文件。
REST API
下面我们介绍云存档相关的 REST API 接口。 开发者可以自行编写程序或脚本调用这些接口在服务端进行管理性质的操作。
请求格式
对于 POST 和 PUT 请求,请求的主体必须是 JSON 格式,而且 HTTP Header 的 Content-Type 需要设置为 application/json
。
请求的鉴权是通过 HTTP Header 里面包含的键值对来进行的,参数如下表:
Key | Value | 含义 | 来源 |
---|---|---|---|
X-LC-Id | {{appid}} | 当前应用的 App Id (即 Client Id ) | 可在控制台查看 |
X-LC-Key | {{appkey}} | 当前应用的 App Key (即 Client Token ) | 可在控制台查看 |
X-LC-Session | <sessionToken> | 玩家的登录凭证 |
管理接口需要使用 Master Key
:X-LC-Key: {{masterkey}},master
。
Master Key
即 Server Secret
,同样可在控制台查看。使用管理接口时无需携带 sessionToken
。
详见文档关于应用凭证的说明。
云存档限制只有添加存档的玩家本人可读可写,因此调用云存档的 REST API 接口时均需在 X-LC-Session
HTTP 头中携带玩家的 sessionToken
或使用 Master Key
,否则请求会因权限不足而失败。
Base URL
REST API 请求的 Base URL(下文 curl 示例中用 {{host}}
表示)即应用绑定的 API 自定义域名,可以在控制台绑定、查看。
详见文档关于域名的说明。
接口列表
接口名称 | 接口请求方法 | 接口地址 | 接口描述 |
---|---|---|---|
获取存档 | GET | /gamesaves/:id | 根据 id 来获取存档记录 |
查询存档 | GET | /gamesaves | 根据查询条件查询存档 |
添加存档 | POST | /gamesaves | 增加新存档 |
更新存档 | PUT | /gamesaves/:id | 根据 id 更新存档 |
删除存档 | DELETE | /gamesaves/:id | 根据 id 删除文档 |
获取存档
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
https://API_BASE_URL/1.1/gamesaves/<objectId>
返回示例:
{
"updatedAt": "2021-08-16T09:18:30.093Z",
"progressValue": 123,
"name": "dennis",
"objectId": "611a2d65bcf94a3222b6d5f3",
"createdAt": "2021-08-16T09:18:29.761Z",
"gameFile": {
"__type": "Pointer",
"className": "_File",
"objectId": "60d1af149be3180684000002"
},
"summary": "hello",
"modifiedAt": {
"__type": "Date",
"iso": "2015-06-21T18:02:52.249Z"
},
"user": {
"__type": "Pointer",
"className": "_User",
"objectId": "5b62c15a9f54540062427acc"
}
}
各个字段的含义请参考云存档保存的游戏元数据。
查询存档
可以通过 where
参数指定查询条件查询存档:
curl -X GET \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
-H "Content-Type: application/json" \
-G \
--data-urlencode 'where={"progressValue":123}' \
https://API_BASE_URL/1.1/gamesaves
返回示例:
{
"results": [
{
"updatedAt": "2021-08-16T09:30:20.643Z",
"name": "dennis",
"createdAt": "2021-08-16T09:30:20.643Z",
"gameFile": {
"__type": "Pointer",
"className": "_File",
"objectId": "60d1af149be3180684000002"
},
"summary": "hello",
"modifiedAt": {
"__type": "Date",
"iso": "2015-06-21T18:02:52.249Z"
},
"objectId": "611a302cbcf94a3222b6d687"
}
]
}
where
的用法详见数据存储 REST API 指南
添加存档
添加存档时的必填字段和可选字段请参考云存档保存的游戏元数据。
在调用添加存档接口前,请先创建 gameFile
和 cover
指向的文件,并确保文件的 ACL 为仅限当前用户读取。
curl -X POST \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
-H "Content-Type: application/json" \
-d '{
"progressValue":123,
"playedTime":1283490343,
"name":"dennis",
"gameFile":{"id":"55a39634e4b0ed48f0c1845c", "__type":"File"},
"cover":{"id": "543cbaede4b07db196f50f3c", "__type": "File"},
"summary":"hello",
"modifiedAt":{"__type":"Date", "iso":"2015-06-21T18:02:52.249Z"}
}' \
https://API_BASE_URL/1.1/gamesaves
成功创建时会返回 objectId 和创建时间:
{"objectId":"611a3407bcf94a3222b6d789", "createdAt":"2021-08-16T09:46:47.290Z"}
失败时会报错,例如:
gameFile is required.
:遗漏了必填字段gameFile
。Forbidden to add new fields by class '_GameSave' permissions.
:提交了非法字段,云存档目前暂不支持添加自定义字段。
删除存档
curl -X DELETE \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
https://API_BASE_URL/1.1/gamesaves/<objectId>
会返回
{}
删除存档时也可以附加 where
条件,防止误删。
参见有条件删除对象。
删除存档时云端会自动删除关联的封面文件和存档原文件。
更新存档
curl -X PUT \
-H "X-LC-Id: {{appid}}" \
-H "X-LC-Key: {{appkey}}" \
-H "X-LC-Session: <sessionToken>" \
-H "Content-Type: application/json" \
-d '{"progressValue": 114514}' \
https://API_BASE_URL/1.1/gamesaves/<objectId>
成功时返回 objectId 和更新时间:
{
"updatedAt": "2021-08-16T09:49:49.579Z",
"objectId": "611a34bdbcf94a3222b6d7af"
}
失败时会报错,例如:
Forbidden to add new fields by class '_GameSave' permissions.
:提交了非法字段,云存档目前暂不支持添加自定义字段。
注意,更新封面文件和存档原文件后,原本关联的封面文件和存档原文件并不会一并删除。 这些文件需要另外删除。 所以一般建议通过删除存档后重新创建存档的方式「更新」存档,这个更新存档的接口主要供服务端在一些管理场景下使用。