zhuxiaojin/rocky-open

落基伟业开放平台 SDK(鉴权/账号/智能体/用户中心/归因渠道)

Maintainers

Package info

github.com/zhuxiaojin/rocky-open

pkg:composer/zhuxiaojin/rocky-open

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-21 02:17 UTC

This package is auto-updated.

Last update: 2026-07-21 02:30:03 UTC


README

包名:zhuxiaojin/rocky-open 适用 PHP 版本:8.0+ 依赖:guzzlehttp/guzzle ^7.10

封装落基伟业开放平台 13 个 API,覆盖鉴权、账号、智能体、用户中心、归因渠道五大模块。

目录

一、安装

composer require zhuxiaojin/rocky-open

二、核心概念

理解三个核心对象,避免使用歧义:

对象 职责 是否有状态 生命周期
Client SDK 入口,持有配置与鉴权方法 无状态(配置不可变) 全局单例
Conversation 已认证会话,聚合所有业务 API,持有 token 有状态(持有 token) 每次调用 withToken() 新建,按需创建
Apis\*Api 业务接口分类,由 Conversation 持有 持有同一 token 随 Conversation 生命周期

调用流程(必读)

┌─────────────┐  withToken(token)   ┌──────────────────┐
│   Client    │ ──────────────────→ │   Conversation   │
│  (无状态)   │                     │  (持有 token)    │
│             │                     │                  │
│ • auth()    │                     │ • account()      │
│ • withToken │                     │ • agent()        │
│ • setBaseUrl│                     │ • user()         │
└─────────────┘                     │ • channel()      │
        ↑                           └──────────────────┘
        │                                   │
        │ 业务方负责 token 缓存              │ 方法内自动注入 Bearer 头
        │ (Redis/文件/内存任选)             │ 调用方无需传 token
        └───────────────────────────────────┘

Token 分类与使用范围(关键业务说明)

Token 类型 获取方法 适用接口 TTL
企业 token Client::auth()->getCompanyToken() 账号、用户中心、归因渠道、换取账号 token 7 天(604800s)
账号 token Client::auth()->getAccountToken($accountId, $companyToken) 智能体(会话、列表、知识库) 7 天(604800s)

业务说明

  • 企业 token 用签名换取(需要 app_key/app_secret)
  • 账号 token 用企业 token 换取(需要指定 accountId)
  • 智能体相关接口必须用账号 token,不能用企业 token
  • SDK 不缓存 token,业务方必须自行缓存与续期(见 Token 缓存策略

三、快速开始

use Zhuxiaojin\RockyOpen\Client;

// 1. 创建 Client(全局单例,配置不可变)
$client = new Client([
    'app_key'    => 'your-app-key',
    'app_secret' => 'your-app-secret',
    // base_url 默认 https://rocky-micro.gantanhao.vip/gateway,可不传
]);

// 2. 获取企业 token(业务方自行缓存,见下文 Token 缓存策略)
$companyToken = $client->auth()->getCompanyToken()['token'];

// 3. 用企业 token 创建会话,调用账号/用户/渠道接口
$conversation = $client->withToken($companyToken);
$conversation->account()->getDetail(13);
$conversation->user()->detail('935641859206959105');
$conversation->channel()->list();

// 4. 调用智能体接口:先用企业 token 换账号 token,再创建新会话
$accountToken = $client->auth()->getAccountToken(15, $companyToken)['token'];
$agentConv = $client->withToken($accountToken);
$agentConv->agent()->list();
$agentConv->agent()->conversation([
    'id'       => 'agent-665c8d42-a9ba-4ed2-9b91-facfb26fb29e',
    'platform' => 'aliyun',
    'messages' => [['role' => 'user', 'content' => '你好']],
]);

配置项

参数 类型 必填 默认值 说明
app_key string - 应用 Key(OPEN 开头)
app_secret string - 应用 Secret
base_url string https://rocky-micro.gantanhao.vip/gateway API 网关地址
timeout float 30.0 请求超时秒数

四、完整 API 参考

4.1 鉴权 Client::auth()

getCompanyToken(array $extra = []): array

获取企业 token。

参数 类型 必填 说明
$extra array 额外参与签名的业务参数(按服务端要求传入,默认不传)

返回['token' => string, 'ttl' => int](ttl 单位秒)

示例

$result = $client->auth()->getCompanyToken();
// ['token' => 'eyJ0eXAi...', 'ttl' => 604800]

getAccountToken(int $accountId, string $companyToken): array

获取账号 token(用于智能体接口)。

参数 类型 必填 说明
$accountId int 账号 ID(业务方上游账号 ID)
$companyToken string 企业 token

返回['token' => string, 'ttl' => int]

示例

$result = $client->auth()->getAccountToken(15, $companyToken);
// ['token' => 'eyJ0eXAi...', 'ttl' => 604800]

4.2 账号 Conversation::account()

使用企业 token 创建会话

getDetail(int $id): array

获取账号详情。

参数 类型 必填 说明
$id int 账号 ID

返回字段

字段 类型 说明
id int 账号 ID
name string 姓名
mobile string 手机号
status mixed 状态
avatar string 头像 URL
company_account_id string 业务方账号 ID

示例

$conversation = $client->withToken($companyToken);
$detail = $conversation->account()->getDetail(13);
// ['id' => 13, 'name' => '赵云', 'mobile' => '', ...]

create(array $data): array

同步账号信息(创建或更新)。

参数 类型 必填 说明
$data['name'] string 姓名
$data['mobile'] string 手机号
$data['avatar'] string 头像 URL
$data['company_account_id'] string 业务方账号 ID

返回['id' => int](上游对应 ID)

示例

$result = $conversation->account()->create([
    'name'               => '赵云',
    'mobile'             => '',
    'avatar'             => '',
    'company_account_id' => '12398',
]);
// ['id' => 13]

4.3 智能体 Conversation::agent()

使用账号 token 创建会话(必须先用企业 token 换取账号 token)

list(): array

获取智能体列表。

返回:智能体数组,每个元素字段:

字段 类型 说明
id string 智能体 ID(如 agent-xxx-xxx
name string 智能体名称
icon string 图标 URL
status int 状态
publish int 发布状态
model_id string|null 模型 ID
platform_model object|null 平台模型信息
business_scene object|null 业务场景
created_at string 创建时间

示例

$agents = $agentConv->agent()->list();
// [['id' => 'agent-32ed3355...', 'name' => 'test', ...], ...]

knowledgeList(): array

查询知识库列表。

返回:知识库数组

示例

$kbList = $agentConv->agent()->knowledgeList();

conversation(array $params): array

发起智能体会话。

参数 类型 必填 说明
$params['id'] string 智能体 ID(如 agent-xxx
$params['platform'] string 平台标识(如 aliyun
$params['messages'] array 二选一 消息列表(传入时不能传 conversation_id
$params['message'] array 二选一 单条消息
$params['conversation_id'] string 会话 ID(续接会话时传)
$params['model_id'] string 模型 ID
$params['notify_url'] string 异步回调地址(携带则转为异步回调)

messages 元素结构

[
    'role'     => 'user',           // 角色
    'content'  => '你好',            // 内容
    'metadata' => [                 // 元数据
        'platform_id'              => 1,
        'corp_id'                  => 3,
        'employee_id'              => 2,
        'session_id'               => 1,
        'session_msg_id'           => 1,
        'internal_user_id'         => '221',
        'external_contact_id'      => 72513,
        'ai_request_context_id'    => 1,
        'ai_request_context_request_id' => 'req_xxx',
        'agent_switch_key'         => 1,
    ],
    'vars' => [],                   // 变量
]

返回字段

字段 类型 说明
run_id string 本次对话 ID
conversation_id string 会话 ID
message.content string Agent 回复内容
message.role string 固定 assistant
message.used_time string 耗时(秒)
metadata object 元数据
usage.input_tokens int 输入 token 数
usage.output_tokens int 输出 token 数

示例

$result = $agentConv->agent()->conversation([
    'id'       => 'agent-665c8d42-a9ba-4ed2-9b91-facfb26fb29e',
    'platform' => 'aliyun',
    'messages' => [
        ['role' => 'user', 'content' => '最近有什么号卡'],
    ],
    // 'conversation_id' => '894706989361778689',  // 续接会话时传
    // 'notify_url' => 'https://your.com/callback', // 异步回调
]);
// [
//     'run_id' => 'run-6402be45-...',
//     'conversation_id' => '894706989361778689',
//     'message' => ['content' => '我知道,请稍后', 'role' => 'assistant', 'used_time' => '2.27'],
//     ...
// ]

4.4 用户中心 Conversation::user()

使用企业 token 创建会话

sync(array $data): mixed

用户同步(创建或更新用户)。

参数 类型 必填 说明
$data['company_user_id'] string 业务方自己的用户 ID
$data['private_data'] array 唯一属性(见下)
$data['identity_data'] array 私有属性(见下)
$data['append_data'] array 补充属性(见下)

private_data 结构(唯一属性):

字段 类型 说明
mobile string 手机号
unionid string 微信 unionid
email string 邮箱
avatar string 头像
nickname string 昵称
extra object 业务方自定义属性
registered_at string 注册时间
remark string 备注
channel_id string 归因渠道 ID

identity_data 结构(私有属性):

字段 类型 说明
gender int 性别:0未知 / 1男 / 2女
birthday string 生日(如 1989-03-01
province string
city string
district string
real_name string 真实姓名
id_card string 身份证

append_data 结构(补充属性,针对游客类身份):

字段 类型 说明
external_user_id string 企微用户 ID
openid string 公众号微信 ID

示例

$result = $conversation->user()->sync([
    'company_user_id' => '0981737618913123273',
    'private_data' => [
        'mobile'       => '13780964504',
        'unionid'      => '',
        'email'        => '',
        'avatar'       => '',
        'nickname'     => '昵称',
        'extra'        => new \stdClass(),
        'registered_at'=> '',
        'remark'       => '',
        'channel_id'   => '',
    ],
    'identity_data' => [
        'gender'   => 0,
        'birthday' => '1989-03-01',
        'province' => '',
        'city'     => '',
        'district' => '',
        'real_name'=> '',
        'id_card'  => '',
    ],
    'append_data' => [
        'external_user_id' => '',
        'openid'           => '',
    ],
]);

completeInfo(array $data): mixed

用户信息补充(手机号等)。

参数 类型 必填 说明
$data['user_id'] string 用户 ID
$data['mobile'] string 手机号
$data['unionid'] string 微信 unionid
$data['openid'] string 微信 openid
$data['company_user_id'] string 业务方用户 ID

示例

$result = $conversation->user()->completeInfo([
    'user_id'         => '935641859206959105',
    'mobile'          => '13154393691',
    'unionid'         => '6666g6',
    'openid'          => '8888g8',
    'company_user_id' => '3_85698',
]);

detail(string $userId): array

用户详情。

参数 类型 必填 说明
$userId string 用户 ID

返回字段

字段 类型 说明
id int 用户 ID
gender int 性别
birthday string 生日
real_name string 真实姓名
province/city/district string|null 地区
merge int 合并标记
top_user_id int 顶层用户 ID
phones array 手机号列表 [['user_id'=>..., 'phone'=>...]]
identities array 身份列表
company_profiles array 公司档案列表
created_at/updated_at string 时间戳

示例

$detail = $conversation->user()->detail('935641859206959105');
// ['id' => 933728952886042624, 'gender' => 1, 'phones' => [...], ...]

4.5 归因渠道 Conversation::channel()

使用企业 token 创建会话

list(): array

渠道列表(不分页)。

返回:渠道数组,每个元素:

字段 类型 说明
id int 渠道 ID
company_id int 公司 ID
name string 渠道名称
created_at string 创建时间
updated_at string 更新时间

paginate(string $name, int $pageSize = 15): array

渠道分页列表。

参数 类型 必填 默认 说明
$name string - 渠道名称(模糊查询,传空字符串查全部)
$pageSize int 15 每页条数

返回(Laravel 分页结构):

字段 类型 说明
current_page int 当前页
data array 数据列表
total int 总数
per_page int 每页条数
last_page int 末页
next_page_url string|null 下一页 URL
prev_page_url string|null 上一页 URL

add(string $name): array

添加渠道。

参数 类型 必填 说明
$name string 渠道名称

返回:新建渠道对象(含 idnamecompany_idcreated_atupdated_at

五、异常处理

所有接口失败均抛 Zhuxiaojin\RockyOpen\RockyOpenException

use Zhuxiaojin\RockyOpen\RockyOpenException;

try {
    $conversation->account()->getDetail(13);
} catch (RockyOpenException $e) {
    $e->getMessage();          // 错误信息(如 "账号不存在")
    $e->getBusinessCode();     // 业务码(int|string|null,如 400、403)
    $e->getResponse();         // 原始响应(array 或 string,调试用)
}

异常场景

场景 业务码 说明
业务错误 400/403/500 等 服务端返回的业务错误,code 字段非 200
HTTP 失败 0 网络异常、超时等,getMessage() 含 Guzzle 异常信息
响应解析失败 0 返回非 JSON,getResponse() 为原始字符串
参数校验 0 SDK 本地校验失败(如 conversationid 参数)

六、Hyperf 协程接入(重点)

6.1 为什么需要特殊处理

SDK 默认使用 Guzzle 的 CurlMultiHandler,在 Swoole 协程环境下会阻塞整个 worker 进程而非当前协程,导致并发性能退化。必须替换为协程化 Handler。

6.2 配置定义

// config/autoload/rocky_open.php
<?php
declare(strict_types=1);

return [
    'app_key'    => env('ROCKY_APP_KEY', ''),
    'app_secret' => env('ROCKY_APP_SECRET', ''),
    'base_url'   => env('ROCKY_BASE_URL', ''), // 留空使用 SDK 默认地址
    'timeout'    => (float) env('ROCKY_TIMEOUT', 30),
];
# .env
ROCKY_APP_KEY=your-app-key
ROCKY_APP_SECRET=your-app-secret
# ROCKY_BASE_URL=  # 留空使用默认
# ROCKY_TIMEOUT=30

6.3 Client 工厂(协程化关键)

// app/Provider/RockyOpenClientFactory.php
<?php
declare(strict_types=1);

namespace App\Provider;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\HandlerStack;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\CoroutineHandler;
use Psr\Container\ContainerInterface;
use Zhuxiaojin\RockyOpen\Client;

class RockyOpenClientFactory
{
    public function __invoke(ContainerInterface $container): Client
    {
        $config = $container->get(ConfigInterface::class)->get('rocky_open', []);

        // ★ 关键:使用 Hyperf 的协程化 Handler
        $handler = new CoroutineHandler();
        $stack   = HandlerStack::create($handler);
        $guzzle  = new GuzzleClient([
            'handler' => $stack,
            'timeout' => (float) ($config['timeout'] ?? 30),
        ]);

        return new Client($config, $guzzle);
    }
}
// config/dependencies.php
<?php
declare(strict_types=1);

return [
    \Zhuxiaojin\RockyOpen\Client::class => \App\Provider\RockyOpenClientFactory::class,
];

6.4 Token 缓存(协程安全)

SDK 不缓存 token,业务方必须自行实现。Hyperf 下推荐用 Redis:

// app/Service/RockyTokenService.php
<?php
declare(strict_types=1);

namespace App\Service;

use Hyperf\Redis\Redis;
use Zhuxiaojin\RockyOpen\Client;

class RockyTokenService
{
    public function __construct(
        private Client $client,
        private Redis $redis,
    ) {}

    /**
     * 获取企业 token(自动缓存与续期)
     */
    public function getCompanyToken(): string
    {
        $key = 'rocky:company_token';
        $token = $this->redis->get($key);
        if (!empty($token)) {
            return $token;
        }

        $result = $this->client->auth()->getCompanyToken();
        // 提前 60 秒过期,避免临界过期
        $this->redis->setex($key, $result['ttl'] - 60, $result['token']);
        return $result['token'];
    }

    /**
     * 获取账号 token(按 accountId 分别缓存)
     */
    public function getAccountToken(int $accountId): string
    {
        $key = "rocky:account_token:{$accountId}";
        $token = $this->redis->get($key);
        if (!empty($token)) {
            return $token;
        }

        $companyToken = $this->getCompanyToken();
        $result = $this->client->auth()->getAccountToken($accountId, $companyToken);
        $this->redis->setex($key, $result['ttl'] - 60, $result['token']);
        return $result['token'];
    }
}

注册到容器:

// config/dependencies.php
return [
    \Zhuxiaojin\RockyOpen\Client::class => \App\Provider\RockyOpenClientFactory::class,
    \App\Service\RockyTokenService::class => \App\Service\RockyTokenService::class,
];

6.5 业务层使用(协程安全模式)

<?php
declare(strict_types=1);

namespace App\Service;

use Hyperf\Redis\Redis;
use Zhuxiaojin\RockyOpen\Client;
use App\Service\RockyTokenService;

class UserService
{
    public function __construct(
        private Client $client,
        private RockyTokenService $tokenService,
    ) {}

    public function getUserDetail(string $userId): array
    {
        // 1. 获取 token(命中 Redis 缓存,多数情况无 HTTP 开销)
        $companyToken = $this->tokenService->getCompanyToken();

        // 2. 每次调用创建独立 Conversation(协程内创建,协程间互不影响)
        $conversation = $this->client->withToken($companyToken);

        // 3. 调用业务接口
        return $conversation->user()->detail($userId);
    }
}

6.6 协程环境关键约束(必读)

约束 说明 违反后果
必须注入 CoroutineHandler 见 6.3,或确保 SWOOLE_HOOK_ALL 启用 阻塞整个 worker,并发退化
Client 可单例 无状态,配置不可变 -
Conversation 不可单例 持有 token,必须每次 withToken() 新建 协程间 token 串用
不要运行时改配置 setBaseUrl() 影响所有协程 竞态条件
Token 必须用 Redis 缓存 内存缓存在协程间不共享,每次请求重新获取 token 造成浪费 性能下降
避免在协程内 sleep Hyperf\Coroutine\Coroutine::sleep() 代替原生 sleep 阻塞 worker

6.7 完整调用示例(Hyperf Controller)

<?php
declare(strict_types=1);

namespace App\Controller;

use App\Service\RockyTokenService;
use Zhuxiaojin\RockyOpen\Client;
use Zhuxiaojin\RockyOpen\RockyOpenException;

class AgentController extends AbstractController
{
    public function __construct(
        private Client $client,
        private RockyTokenService $tokenService,
    ) {}

    public function chat()
    {
        $params = $this->request->all();
        $accountId = (int) $this->request->input('account_id', 15);

        try {
            // 1. 换账号 token(Redis 缓存命中时无 HTTP 开销)
            $accountToken = $this->tokenService->getAccountToken($accountId);

            // 2. 创建会话(协程内独立)
            $conversation = $this->client->withToken($accountToken);

            // 3. 发起会话
            $result = $conversation->agent()->conversation([
                'id'       => $params['agent_id'],
                'platform' => $params['platform'] ?? 'aliyun',
                'messages' => $params['messages'],
            ]);

            return $this->response->json(['code' => 0, 'data' => $result]);
        } catch (RockyOpenException $e) {
            return $this->response->json([
                'code' => $e->getBusinessCode() ?? -1,
                'msg'  => $e->getMessage(),
            ]);
        }
    }
}

七、常见问题

Q1: 调用渠道接口返回 403 "The token does not support the current scene"

原因:渠道接口路径为 /web-api/...,企业 token 的 scene 是 open,不支持 web-api

解决:渠道接口需要后台 token,开放平台 SDK 暂不支持。如需调用,联系平台方确认 token 类型。

Q2: 调用智能体接口报 "该账号不存在"

原因getAccountToken($accountId, ...)$accountId 必须是上游真实存在的账号 ID。

解决:先用 account()->create([...]) 同步账号,用返回的 id 作为 accountId。

Q3: Token 缓存策略

推荐做法

// 用 Redis 缓存,提前 60 秒过期避免临界
$key = 'rocky:company_token';
if ($token = $redis->get($key)) {
    return $token;
}
$result = $client->auth()->getCompanyToken();
$redis->setex($key, $result['ttl'] - 60, $result['token']);
return $result['token'];

避免做法

  • ❌ 用静态变量/单例属性缓存(协程间不共享)
  • ❌ 每次请求都重新获取(浪费 HTTP 往返)

Q4: 如何切换不同环境(测试/生产)

通过 base_url 配置切换:

// 测试环境
$client = new Client([
    'app_key'    => 'xxx',
    'app_secret' => 'xxx',
    'base_url'   => 'https://test-host/gateway',
]);

// 生产环境(使用默认)
$client = new Client([
    'app_key'    => 'xxx',
    'app_secret' => 'xxx',
    // base_url 不传,默认 https://rocky-micro.gantanhao.vip/gateway
]);

Q5: 异步会话回调

conversation()notify_url 后转为异步,接口立即返回,结果通过回调推送:

$conversation->agent()->conversation([
    'id'        => 'agent-xxx',
    'platform'  => 'aliyun',
    'messages'  => [...],
    'notify_url'=> 'https://your-domain.com/callback/rocky',
]);
// 接口立即返回,实际结果 POST 到 notify_url

回调地址需自行实现接收端点。