跳到主要内容
版本:v3

Tap 客服开发指南

本文介绍如何在游戏中接入 TDS 提供的客服系统。

权限说明

SDK 初始化

下载页 获得 TapSDK,引入 TapSupport 模块:

SDK 可以通过 Unity Package Manager 导入或手动导入,二者任选其一。请根据项目需要选择。

方法一:使用 Unity Package Manager

从 3.29.1 版本开始, SDK 修改 JSON 解析库为 Newtonsoft-json,如果当前工程已接入该依赖库,则不需额外处理,否则需在 Packages/manifest.json 添加如下依赖:

"com.unity.nuget.newtonsoft-json":"3.2.1"
NPMJS 安装

从 3.25.0 版本开始,TapSDK 支持了 NPMJS 安装,优势是只需要配置版本号,并且支持嵌套依赖。

在项目的 Packages/manifest.json 文件中添加以下依赖:

"dependencies":{
"com.taptap.tds.support":"3.29.4",
}

但需要注意的是,要在 Packages/manifest.jsondependencies 同级下声明 scopedRegistries

"scopedRegistries": [
{
"name": "NPMJS",
"url": "https://registry.npmjs.org/",
"scopes": ["com.tapsdk", "com.taptap", "com.leancloud"]
}
]

GitHub 安装

在项目的 Packages/manifest.json 文件中添加以下依赖:

"dependencies":{
"com.taptap.tds.common":"https://github.com/TapTap/TapCommon-Unity.git#3.29.4",
"com.taptap.tds.support":"https://github.com/TapTap/TapSupport-Unity.git#3.29.4",
"com.leancloud.storage":"https://github.com/leancloud/csharp-sdk-upm.git#storage-2.3.0",
}

在 Unity 顶部菜单中选择 Window > Package Manager 可查看已经安装在项目中的包。

方法二:手动导入

  1. 下载页 找到 TapSDK Unity 下载地址,下载 TapSDK-UnityPackage.zip

  2. 在 Unity 项目中依次转到 Assets > Import Packages > Custom Packages,从解压后的 TapSDK-UnityPackage.zip 中,选择希望在游戏中使用的 TapSDK 包导入,其中:

    • TapTap_Common.unitypackage TapSDK 基础库,必选
    • TapTap_Support.unitypackage TapTap 客服库,必选
  3. 如果当前项目已集成 Newtonsoft.Json 依赖,则忽略该步骤,否则在 NuGet.org Newtonsoft.Json 页面中通过点击右侧 「Download package」 下载库文件,并将下载的文件后缀从.nupkg 修改为 .zip,同时解压该文件并复制内部的 Newtonsoft.Json.dll 文件拷贝到工程 AssetsPlugins 目录下,另外为了避免导出 IL2CPP 平台时删除必要数据,需在 Assets 目录下创建 link.xml 文件(如果已有该文件,则添加如下内容),其内容如下:

<linker>
<assembly fullname="System.Core">
<type fullname="System.Linq.Expressions.Interpreter.LightLambda" preserve="all" />
</assembly>
</linker>

在初始化 SDK 之前,有一些准备工作需要完成:

  1. 请厂商管理员或客服管理员邀请你成为客服管理员。
  2. 每个厂商会被分配一个专属域名,需要记下待用。你可以在客服工作台的 设置 > 开发者信息 中找到可用的域名。如果你想要使用自己的域名,可以联系厂商管理员在开发者中心游戏客服模块中绑定。
  3. 在客服工作台,为你的游戏创建一个产品(设置 > 管理 > 产品与分类),记下这个产品的 ID。

然后使用以下代码初始化 TapSupport 模块:

using TapTap.Support;

TapSupport.Init("https://please-replace-with-your-customized.domain.com", "产品 ID");

上述代码示例中,please-replace-with-your-customized.domain.com 就是准备工作 2 中获取或绑定的域名。产品 ID 是在准备工作 3 中新建的产品的 ID。

登录

为了确保玩家提交的表单等信息只有该玩家能访问,客服系统需要登录才能使用。我们提供三种不同的登录方式:

  • TDS 内建账户(TDSUser)登录
  • 游戏自有账户系统关联登录
  • 匿名登录
备注

这里的登录指的是游戏玩家在游戏内使用客服系统功能时认证身份的过程(authentication)。这一步主要是游戏侧与客服系统之间的交互,通常玩家是无感知的,和游戏本身的登录是两回事(比如这里的匿名登录与游戏的游客登录无关)。

TDS 内建账户登录

如果你的游戏使用了 TDS 内建账户服务,可以直接在客户端使用已登录的 TDSUser 授权登录客服系统。

信息

TDS 内建账户服务是 TDS 提供的支持多种登录方式的用户系统。如果你的游戏已经在使用 TapTap 登录、好友、成就、排行榜等服务,你很可能已经在使用 TDS 内建账户了。更多介绍,请参考《内建账户功能介绍》

要使用 TDSUser 授权登录客服系统,先要使用一个已登录的 TDS 用户账号请求授权 token,然后使用该 token 调用客服模块的 TDS 登录接口:

try {
string token = await TDSUser.RetrieveShortToken();
await TapSupport.LoginWithTDSCredential(token);
Debug.Log("登录 TDSUser JWT 完成");
} catch (TapException e) {
Debug.LogError($"{e.Code} : {e.Message}");
} catch (Exception e) {
Debug.Log(e);
}

异常处理

如果 SDK 在登录以及后续流程中发生了异常,开发者可以通过回调得到通知。

在这些异常中,我们需要特别处理 token 过期(EXPIRED_CREDENTIAL)异常,重新执行一遍上面的「请求授权 token,调用登录接口」的流程重新登录客服。

对于其他种类的异常,建议直接向玩家展示。由于客服功能异常通常不影响玩家的游玩体验,可以考虑仅在客服入口处提示玩家客服不可用,并提供手动触发重试的交互。


if (e is TapException ex && ex.Code == 9006) {
// 登录过期
} else {
// 其他异常
}

游戏自有账户系统关联登录

如果你的游戏使用了其他用户系统,客服系统也支持使用经过签名的用户信息来登录。

需要服务端

对用户信息进行签名需要使用 secret,因此必须有服务端配合才能使用该方式登录。

开发者首先需要在客服工作台的 设置 - 开发者设置 - 玩家鉴权 中生成一个 auth secret。然后在服务端使用这个 secret 用 HS256 算法对玩家信息进行 JWT 签名。玩家信息(payload)应该包含用户的唯一标识与展示用的名称,结构如下:

{
"sub": "U1234567", // 唯一标识
"name": "Dash" // 展示在工单、客服后台的名称
}
JWT 签名示例
输入:
  • 算法: HS256
  • payload: {"sub": "U1234567", "name": "Dash"}
  • secret: 44a23a3701955756301768bbb5dd1e1ea51500b556fb73201de76d5365150653

输出 JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJVMTIzNDU2NyIsIm5hbWUiOiJEYXNoIn0.OV2LCP-7cLVTfjJlx21q9O1Tj_LlM0LFyW3OOu7CeNk

最后将得到的 JWT 返回给客户端,客户端调用 SDK 的第三方账号登录接口完成登录:

TapSupport.LoginWithCustomCredential(jwt);

出于安全考虑,我们通常还会给 JWT 的 payload 增加 exp 字段来指定其过期时间,客服系统会尊重 JWT 的过期时间设置:

{
"sub": "U1234567",
"name": "Dash",
"exp": 1676546560 // Unix epoch
}

综合考虑安全性与性能,我们建议将 JWT 的过期时间设为(当前时间往后) 1-3 天。

异常处理

如果 SDK 在登录以及后续流程中发生了异常,开发者可以通过回调得到通知。

在这些异常中,我们需要特别处理 JWT 过期(EXPIRED_CREDENTIAL)异常,重新执行一遍上面的「请求 JWT,调用登录接口」的流程重新登录客服。

对于其他种类的异常,建议直接向玩家展示。由于客服功能异常通常不影响玩家的游玩体验,可以考虑仅在客服入口处提示玩家客服不可用,并提供手动触发重试的交互。


if (e is TapException ex && ex.Code == 9006) {
// 登录过期
} else {
// 其他异常
}

匿名登录

匿名登录是指游戏使用一个只有当前玩家能拿到的字符串作为匿名身份标识(ID)登录客服系统。

TapSupport.LoginAnonymously("uuid");

匿名登录可以用来在游戏没有账号系统或者玩家未登录的场景下按照设备区分玩家,也可以在玩家登录游戏账号后按账号区分玩家。

与设备关联

游戏客户端为设备生成并持久化一个 UUID,然后使用该 ID 调用匿名登录接口即可实现玩家与设备关联。

此时,设备上的匿名 ID 是唯一的身份凭证,如果因为删除应用或清除本地数据等原因丢失了该 ID,玩家将无法访问历史工单等数据。

与游戏账号关联

类似的,要与游戏账号关联,需要使用一个与账号一对一关系的 ID 调用匿名登录接口。

匿名登录机制非常灵活。举个例子,一个厂商的多个游戏通过「通行证」账号统一登录,如果希望一个玩家在每个游戏的客服账号是独立的,可以给每个游戏分配一个独立的匿名 ID;如果希望跨游戏的追踪玩家,就用通行证级别的匿名 ID。这种方式中客服系统是完全不知道这个 ID 意味着什么的,这也是「匿名」这个名字的由来。

灵活的代价是安全。如果使用不当,匿名登录方式可能导致数据泄露。客服系统不关心这个匿名 ID 的来源,也无法进行任何的校验,这意味着泄露了这个 ID 玩家的历史工单数据也会被泄露。因此,「只有当前玩家能拿到」需要游戏侧自行保证。更具体的说,匿名 ID 需满足以下条件:

  • 玩家唯一且不会变
  • 无法被猜测或推导、无法枚举
  • 非公开,不会被玩家通过分享或截图等方式泄露
正确的匿名 ID 示例
  • b3a59993-c659-49f9-9f51-f2c808a472a0:游戏用户系统在创建新玩家时生成一个 UUID 作为该玩家的客服系统匿名 ID。玩家登录后才能拿到该 ID 登录客服系统。
  • sha1('2436234209' + secret):在服务端对玩家 UID append 一个固定的 secret 然后哈希。玩家登录后才能拿到该 ID 登录客服系统。
不安全的匿名 ID 示例

简单的说,纯客户端的方案都不安全:

  • '2436234209' 玩家 UID:这通常是公开信息,玩家在游戏中可见且可能被分享,并且纯数字 ID 很容易枚举。
  • sha1('2436234209') :单纯的哈希,被猜到算法后依然很容易枚举。

为了降低被误用的风险,匿名登录接口限制了匿名 ID 的最小长度为 32。

清除登录状态

TapSupport.Logout();

打开客服页面

TapSupport.OpenSupportView();
信息

如果打开的页面没有内容,可以先在客服工作台为该游戏分类配置一些子分类。

场景化入口

除了落地页,SDK 也支持在特定的场景下直接打开具体的页面。

TapSupport.OpenSupportView();

不同的页面通过不同的 path 参数区分。当前支持的页面有:

path说明
/tickets/new?category_id={id}提交新工单页面,需要指定分类的 id。
/tickets玩家工单列表页,可以看到玩家所有历史工单。
/articles/{id}知识库文章页。

上报信息

工单模块支持开发者通过自定义字段收集设备、玩家等额外信息。开发者可以在打开客服页面(openSupportView)时带上额外的信息。SDK 会在提交工单时将这些信息记录到工单字段中。这些信息可以在客服工作台供客服查看、分析。

在上报数据前,首先需要在客服工作台(设置 > 管理 > 工单字段)定义字段。这些字段需要设置为「仅限客服」访问。创建完成后需要记下该字段的 ID,我们会在 SDK 中用到。

Dictionary<string, object> fields = new Dictionary<string, object>();
fields.Add("243", "iOS 15.1"); // 243 是在后台创建的「OS」字段的 ID
fields.Add("244", "Dash"); // 244 是在后台创建的「角色名」字段的 ID

TapSupport.OpenSupportView("/", fields);

除了在打开客服页面时设置,开发者也可以通过以下接口设置全局默认的字段信息:

TapSupport.SetDefaultFieldsData(fields: fields);

全局字段可以在设置后更新:

TapSupport.DefaultFields = new Dictionary<string, object> {
{ "key", "value" }
};

全局设置的字段会在 OpenSupportView 时与传入的 fields 参数合并。OpenSupportView 方法时传入的字段比全局字段的优先级高,也就是说如果有相同的字段,全局字段不会生效。

关闭客服页面

玩家可以在客服页面内点击关闭按钮退出。但在特定场景下,游戏可能需要主动关闭客服页面:

TapSupport.CloseSupportView();

未读消息通知

当提交的工单有了新的进展(例如有了新的客服回复)时,玩家会产生未读消息。通常在游戏内会在客服入口使用小红点等形式提示玩家有未读消息。SDK 会自动通过轮询获取是否有维度消息,当未读消息状态发生变化时——从无到有或从有到无—— SDK 会通过回调通知开发者:

using TapTap.Support;

TapSupport.Init("https://please-replace-with-your-customized.domain.com", "分类 ID", new TapSupportCallback
{
UnReadStatusChanged = (hasUnRead, exception) =>
{
Debug.Log($"hasUnRead:{hasUnRead} exception:{exception}");
}
});
信息

开发者无需额外在点击客服入口时清除本地的未读通知状态(小红点)。因为点开了客服入口不意味着玩家查看了所有未读的工单。如果玩家查看了所有未读工单,SDK 会及时获取到最新的状态并通过上文提到的回调通知开发者。

暂停轮询

SDK 内建的轮询机制会智能的调整频率获取未读消息状态。但在有些场景下这些请求依然是没有必要的开销,例如玩家在游戏对局期间(此时界面上都没有展示小红点的地方)希望能暂停一切不必要的后台请求。SDK 为此提供了一对 API 来控制轮询:

  • Pause:暂停轮询
  • Resume:恢复轮询
TapSupport.Resume();
TapSupport.Pause();
SDK 的轮询策略
  • 初始时立即发起一次请求,设定下一次请求间隔 10s。
    • 如果某一次未读消息状态与当前状态对比没有变化,增加 10s 间隔时间,直到最大间隔时间 300s。如果发生了变化,重置间隔时间为 10s。
    • 如果玩家从未打开过客服页面(WebView),间隔时间增加并恒定为 3600s。
  • 调用 Resume 方法会重置轮询为初始状态。