跳转至

客户后端实现规范

客户后端接口实现

为了保证云游戏系统的正常运行与计费,客户后端需要实现一系列接口,这些接口的返回值为均为字符串。

如果客户不需要对最终用户进行计费,那么客户后端可以做到完全无状态,也不需要在后端请求任何外部服务器,只需要很小的开销即可实现这一组接口。

所有的接口传输细节由客户自行确定,只需要在使用 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

以此类推。