客户后端实现规范
客户后端接口实现
为了保证云游戏系统的正常运行与计费,客户后端需要实现一系列接口,这些接口的返回值为均为字符串。
如果客户不需要对最终用户进行计费,那么客户后端可以做到完全无状态,也不需要在后端请求任何外部服务器,只需要很小的开销即可实现这一组接口。
所有的接口传输细节由客户自行确定,只需要在使用 SDK 时传入对应的函数即可,具体逻辑可自行实现。
我们提供 PHP 语言的后端 SDK 实现供参考。接口规范非常简单,无论是什么语言都应当很容易实现,开发成本小于2小时。
基础
所有的后端接口都需要返回一个 JWT 字符串,其 Payload 基础如下:
{
"iss": "demo", // 随便写点啥,由客户自定义
"aud": "mp", // 固定值 mp
"iat": time(), // 当前时间戳,秒级
"exp": time() + 5 * 60, // token 过期时间,大于等于1分钟小于2小时即可
"customer": "moving", // 客户标识,对每个客户而言唯一
}
签名使用的 key 为客户 secret,签名方法为 HS256 或 HS512。
JWT 规范参考: https://jwt.io
生成的 token 样例见本文档末尾。
用户鉴权
在用户开始排队之前, SDK 需要获取鉴权 token (authToken),并传入如下参数:
session
: 当前的会话 ID,不超过 64 字节的字母数字混合字符串
客户后端需要返回如下结构的 JWT Payload:
{
...(basePayload), // 基础 payload,见第一节
"type": "auth", // 固定值
"user": "123456", // 用户唯一识别符,可以是 id 或者其它任何 64 字节以内的字符串
"queue": "standard", // 队列名字,通常对应不同的硬件配置
"session": "foobar", // 回传 session
}
如果需要计费,建议客户后端在此时保存 user 和 session 的对应关系。
启动游戏
当排队完成、用户准备启动游戏时, SDK 将获取启动 token (startToken),并传入如下参数:
session
: 当前的会话 ID,不超过 64 字节的字母数字混合字符串
客户后端需要返回如下结构的 JWT Payload:
{
...(basePayload), // 基础 payload,见第一节
"type": "start", // 固定值
"session": "foobar", // 回传 session
"queue": "standard", // 队列名字,通常对应不同的硬件配置
}
如果需要计费,建议客户后端此时校验 session 是否属于当前用户。
在此 token 被生成后,游戏可能启动成功或启动失败,故此处不建议对用户进行计费。
计费
当用户进入游戏后,默认拥有 30 秒的游戏时间。当游戏时间小于 30 秒时,SDK 将获取计费 token (renewToken),并传入如下参数:
session
: 当前的会话 ID,不超过 64 字节的字母数字混合字符串lastDeadline
: 当前用户拥有的游戏时间;第一次请求该接口时为0,此后为「第一次请求该接口的时间」到「可用游戏时长结束」的秒数;
客户后端需要返回如下结构的 JWT Payload:
{
...(basePayload), // 基础 payload,见第一节
"type": "renew", // 固定值
"session": "foobar", // 回传 session
"deadline": 90, // 新的游戏结束时间,通常为当前 lastDeadline 加上计费或心跳周期(如60)
}
如果后端不计费,那么直接将传入的 lastDeadline 加上心跳周期时间(如60)回传即可。
如果后端需要计费,每次收到计费 token 请求时,需要对用户进行计费,参考下文的计费细节。
对用户计费
云游戏后端对用户游戏时长的控制策略如下:
- 开始游戏后,有 30 秒的免费游戏时间
- SDK 会在最长 30 秒内(一般几秒内)要求后端发放计费代币(renewToken),代币中包含 deadline,也就是用户可用的游戏时长
- 用户的游戏时长 (playTime) 会从第一次发放计费代币开始累积
- 当 playTime > deadline - 30 时,会重新要求后端发放新的计费代币
- 如果超过 30 秒无法得到新的计费代币,则游戏自动停止
推荐的计费模式如下:
- 首先确定最小计费周期,推荐1分钟
- 第一次发放计费代币(lastDeadline == 0)时,记录当前时间作为计费开始时间,返回 deadline = min(计费周期, 用户剩余时长)
- 后续每次发放计费代币(lastDeadline > 0)时,将当前时间减去计费开始时间作为「游戏时长」,对用户计费;计费后,返回 deadline = 已计费的游戏时长 + min(计费周期, 用户剩余时长)
按如上计费模式操作,对用户计费的时间和真正的游戏时间差距在“30秒+1个计费周期”以内,且对用户只少不多计费。
如果希望对用户只多不少计费,那么可以做出如下调整:
- 在获取 startToken 时,即对用户计费 30 秒
- 在第一次发放计费代币时,将 startToken 时间作为计费开始时间,将当前时间+计费周期作为计费结束时间,进行计费
- 后续每次发放计费代币时,都将当前时间+计费周期作为计费结束时间来对用户计费
此时对用户的计费时间和真正的游戏时间差距依然在“30秒+1个计费周期”以内,且对用户只多不少。
后端接口举例
可在 https://jwt.io/ 解码 JWT。
用户鉴权
request:
GET /api/game/authToken
response:
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZW1vIiwiYXVkIjoibXAiLCJpYXQiOjE2MTY1OTE5NjUsImV4cCI6MTYxNjU5Mjk2NSwiY3VzdG9tZXIiOiJtb3ZpbmciLCJ1c2VyIjoiMTIzNCIsInF1ZXVlIjoic3RhbmRhcmQiLCJzZXNzaW9uIjoiZm9vYmFyIn0.rjytUtU0x2zM_sAIbXzy4zwqWlpaG-aEQEKeaccnp1Y"}
启动游戏
request:
POST /api/game/start
Content-Type: application/json
{"session": "foobar"}
response:
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZW1vIiwiYXVkIjoibXAiLCJpYXQiOjE2MTY1OTE5NjUsImV4cCI6MTYxNjU5Mjk2NSwiY3VzdG9tZXIiOiJtb3ZpbmciLCJxdWV1ZSI6InN0YW5kYXJkIiwic2Vzc2lvbiI6ImZvb2JhciJ9.wQ15uRXlCRz4qfzsehr_ME1R_jTIqeSXolSUOYoUvmg"}
计费
request:
POST /api/game/renew
Content-Type: application/json
{"session": "foobar", "lastDeadline": 0}
response:
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZW1vIiwiYXVkIjoibXAiLCJpYXQiOjE2MTY1OTE5NjUsImV4cCI6MTYxNjU5Mjk2NSwiY3VzdG9tZXIiOiJtb3ZpbmciLCJkZWFkbGluZSI6NjAsInNlc3Npb24iOiJmb29iYXIifQ.SqdLVVs3Rh1WZ9WWG8nYued2bIT7h1H-1efjfKT9HLQ"}
计费请求第1次(0s): lastDeadline=0 响应: deadline=60
计费请求第2次(30s): lastDeadline=60 响应: deadline=120
计费请求第3次(90s): lastDeadline=120 响应: deadline=180
计费请求第4次(150s): lastDeadline=180 响应: deadline=240
以此类推。