客服系统源码对接用户数据-99客服
用户数据对接教程
客服系统可用于电商、金融、通讯、公益等多种场景。在这些场景中,客服系统承担着连接用户与平台的重要角色:既帮助平台识别访客、理解诉求,也帮助用户获得一致、可信赖的服务体验,从而提升品牌认知、满意度与粘性。
在实际对接中,常见需求是:业务系统里已经登录的用户,在发起咨询时,客服端要能识别「是谁在咨询」,并展示其用户标识、头像、昵称,必要时还可同步订单等业务信息。为此,系统预留了用户数据对接能力:在网页脚本或访问地址中传入相应参数,即可完成对接。
本文按「先易后难」说明:明文参数对接、安全与加密对接、以及 PHP / Java 生成加密参数 的示例代码。
一、对接方式概览
系统支持两种入口形态:
| 方式 | 说明 |
|---|---|
| JS 代码接入 | 在页面中嵌入一段脚本,通过全局变量 window._kefu 传入组织标识、分组及用户信息等,由脚本加载客服界面。 |
| URL 直接接入 | 用户或业务系统直接打开访客页地址,通过查询参数传递用户信息。 |
无论哪种方式,常用的用户字段为:
| 参数 | 含义 | 说明 |
|---|---|---|
uid |
用户 ID | 在您业务系统中的唯一标识,必填(明文或解密后均需能识别用户)。 |
name |
昵称 | 可选,未传时系统可使用默认访客名。 |
avatar |
头像地址 | 可选,建议为 https 可访问的完整 URL。 |
此外,脚本中通常还需要配置:
| 参数 | 含义 |
|---|---|
bid |
组织标识,由后台提供。 |
groupid |
客服分组,无特殊需求可填 '0'。 |
domain |
客服系统访问域名,需与部署一致(如 https://kefu.99kf.com)。 |
mini |
1(默认)为页面内弹层;0 为新开窗口。 |
themeColor |
主题色:auto 表示尝试从当前页面自动推断;也可传 #1677ff 等固定色。 |
二、明文对接(简单场景)
明文对接指在 URL 或 window._kefu 中直接传递 uid、name、avatar。接入成本低,适合内网、测试或对伪造风险不敏感的场景。
请注意: 若他人在知道您的 bid 的前提下,理论上可以编造 uid 等参数访问。对安全要求较高的业务,请阅读本文第三节「加密对接」。
2.1 JS 代码接入(当前推荐写法)
下面是一段与当前脚本兼容的接入示例:在 window._kefu 中配置 bid、groupid、domain、mini、themeColor,并传入用户 uid、name、avatar。
<script>
window._kefu = {
bid: 'Z3SmMhtO',
groupid: '0',
domain: 'https://kefu.99kf.com',
mini: 1,
themeColor: 'auto',
uid: '102',
name: '大大',
avatar: 'https://example.com/avatar.jpg'
};
(function () {
var d = document;
function l() {
var a = d.createElement('script');
a.type = 'text/javascript';
a.async = true;
a.charset = 'utf-8';
a.src = 'https://kefu.99kf.com/static/js/kf.js';
var b = d.getElementsByTagName('script')[0];
b.parentNode.insertBefore(a, b);
}
l();
})();
</script>
说明:
uid建议传字符串或数字均可,与业务系统用户主键保持一致即可。themeColor: 'auto'会尝试根据当前页面风格推断主题色;若不需要可省略该项或改为具体颜色值。
2.2 URL 直接接入
用户可直接打开如下形式的地址(示例域名请替换为实际客服域名):
https://kefu.99kf.com/user?bid=Z3SmMhtO&groupid=0&uid=100&name=小小&avatar=https%3A%2F%2Fexample.com%2Favatar.jpg
重要: 使用 URL 传递 avatar(或其它含特殊字符的参数)时,应对参数值做 URL 编码,例如:
- PHP:
urlencode()或rawurlencode() - JavaScript:
encodeURIComponent()
否则可能导致头像无法解析或整段地址被截断。
2.3 对接后的效果
完成对接后,客服侧会话列表与详情中可看到对应用户的标识、头像、昵称等信息(具体界面以实际版本为准)。

三、加密对接(推荐用于生产环境)
3.1 为什么需要加密
明文方式下,仅凭链接或前端脚本即可仿造 uid,存在被恶意利用的风险。为此,系统提供:
- 接口密钥(每组织唯一,长度 32 字符):用于对访客身份数据进行 AES-256-GCM 加密,生成
code参数。 - 是否允许明文传输用户数据(后台配置项):关闭后,不再接受在 URL 中直接携带
uid、name、avatar,必须使用加密后的code访问。
这样,只有掌握接口密钥的一方才能生成有效 code,从而降低伪造身份的风险。
3.2 明文与加密在形式上的区别
明文(允许明文开启时),地址形态类似:
https://kefu.99kf.com/user?bid=您的bid&groupid=0&uid=100&name=昵称&avatar=编码后的头像URL
加密(推荐),不再在查询串中暴露 uid 等明文,而是携带一段 Base64 密文,形态类似:
https://kefu.99kf.com/user?bid=您的bid&groupid=0&code=Base64密文字符串
code 由服务端使用该组织的接口密钥对一段查询字符串(如 uid=...&name=...&avatar=...)加密后得到;访客页首次加载后会重定向到带 code 的规范地址,服务端解密后识别用户。
3.3 密文格式说明(与系统实现一致)
便于与多语言对接,加密规则约定如下:
| 项目 | 说明 |
|---|---|
| 算法 | AES-256-GCM |
| 密钥 | 接口密钥字符串,必须为 32 个字符(UTF-8 下 32 字节) |
| IV | 12 字节,每次加密随机生成 |
| 认证标签 | 16 字节 |
| 二进制排列 | IV(12 字节) + Tag(16 字节) + 密文 |
| 传输 | 对上述二进制做 Base64,作为 code 的值 |
待加密的明文为 UTF-8 编码的查询字符串,与 PHP http_build_query($fields) 默认行为一致,例如:
uid=user001&name=张三&avatar=https%3A%2F%2Fexample.com%2Fa.png
四、生成加密数据:PHP 示例
以下函数生成可放入 URL 的 code(与系统内置逻辑一致)。请将 接口密钥 替换为后台显示的 32 位密钥。
<?php
/**
* @param string $apiKey 32 字符的接口密钥
* @param array $fields 如 ['uid' => 'xxx', 'name' => '昵称', 'avatar' => 'https://...']
*/
function buildVisitorCode(string $apiKey, array $fields): string
{
if (strlen($apiKey) !== 32) {
throw new InvalidArgumentException('接口密钥长度必须为 32');
}
$plain = http_build_query($fields);
$cipher = 'aes-256-gcm';
$ivLen = 12;
$tagLen = 16;
$iv = random_bytes($ivLen);
$tag = '';
$ciphertext = openssl_encrypt(
$plain,
$cipher,
$apiKey,
OPENSSL_RAW_DATA,
$iv,
$tag,
'',
$tagLen
);
if ($ciphertext === false) {
throw new RuntimeException('加密失败');
}
return base64_encode($iv . $tag . $ciphertext);
}
// 使用示例
$apiKey = '0123456789abcdef0123456789abcdef'; // 从后台复制,示例勿用于生产
$code = buildVisitorCode($apiKey, [
'uid' => 'user_' . time(),
'name' => '访客昵称',
'avatar' => 'https://example.com/avatar.png',
]);
$bid = '您的bid';
$url = 'https://kefu.99kf.com/user?bid=' . rawurlencode($bid)
. '&groupid=0&code=' . rawurlencode($code);
拼接完整 URL 时,请对 code 使用 rawurlencode(),避免 +、/ 等字符在传输中被错误解析。
五、生成加密数据:Java 示例(与 PHP 兼容)
Java 中 Cipher 在 AES-GCM 下,doFinal 的结果一般为 「密文 + Tag(16 字节在后)」,而 PHP 侧存储顺序为 「IV + Tag + 密文」。因此需要拆分 doFinal 结果,再按 PHP 相同顺序组装后 Base64。
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
public class VisitorCodeCrypto {
private static final int IV_LEN = 12;
private static final int TAG_BITS = 128;
private static final String TRANS = "AES/GCM/NoPadding";
public static String buildVisitorCode(String apiKey, Map<String, String> fields) throws Exception {
if (apiKey == null || apiKey.length() != 32) {
throw new IllegalArgumentException("接口密钥长度必须为 32");
}
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> e : fields.entrySet()) {
if (!first) sb.append('&');
first = false;
sb.append(java.net.URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8));
sb.append('=');
sb.append(java.net.URLEncoder.encode(String.valueOf(e.getValue()), StandardCharsets.UTF_8));
}
String plain = sb.toString();
byte[] keyBytes = apiKey.getBytes(StandardCharsets.UTF_8);
if (keyBytes.length != 32) {
throw new IllegalArgumentException("接口密钥 UTF-8 字节长度必须为 32");
}
byte[] iv = new byte[IV_LEN];
new SecureRandom().nextBytes(iv);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_BITS, iv);
Cipher cipher = Cipher.getInstance(TRANS);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
byte[] cipherWithTag = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
int tagLen = 16;
int ctLen = cipherWithTag.length - tagLen;
byte[] ciphertext = java.util.Arrays.copyOfRange(cipherWithTag, 0, ctLen);
byte[] tag = java.util.Arrays.copyOfRange(cipherWithTag, ctLen, cipherWithTag.length);
byte[] packed = new byte[iv.length + tag.length + ciphertext.length];
System.arraycopy(iv, 0, packed, 0, iv.length);
System.arraycopy(tag, 0, packed, iv.length, tag.length);
System.arraycopy(ciphertext, 0, packed, iv.length + tag.length, ciphertext.length);
return Base64.getEncoder().encodeToString(packed);
}
}
兼容性要点:
- 最终
code的 Base64 解码后必须为 IV(12) + Tag(16) + 密文。 - 密钥为 32 字符,按 UTF-8 作为 AES 密钥字节使用。
- 与 PHP 联调时,可将 Java 生成的明文字符串与
http_build_query($fields)输出对比,确保一致。
六、加密方式下的 JS 与 URL 示例
6.1 URL 方式
由服务端生成 code 后,拼接:
https://kefu.99kf.com/user?bid=您的bid&groupid=0&code=加密得到的code
6.2 JS 方式
在服务端渲染页面时,将生成的 code 写入 window._kefu.code(与 uid/name/avatar 不要同时使用,以 code 为准):
<script>
window._kefu = {
bid: 'Z3SmMhtO',
groupid: '0',
domain: 'https://kefu.99kf.com',
mini: 1,
themeColor: 'auto',
code: '此处填入服务端生成的code'
};
(function () {
var d = document;
function l() {
var a = d.createElement('script');
a.type = 'text/javascript';
a.async = true;
a.charset = 'utf-8';
a.src = 'https://kefu.99kf.com/static/js/kf.js';
var b = d.getElementsByTagName('script')[0];
b.parentNode.insertBefore(a, b);
}
l();
})();
</script>
七、常见问题
问:接口密钥泄露怎么办?
答:在后台重新生成密钥,并更新所有服务端生成 code 的逻辑。旧密钥产生的 code 将失效。
问:明文和加密可以同时用吗?
答:若配置了 code,接入脚本会优先走加密参数,不再附带明文 uid/name/avatar 查询串。
问:关闭「允许明文」后,旧链接还能用吗?
答:带明文用户参数的链接将被拒绝,请改为使用 code 方式。
