PHP code example of goletter / hyperf-docs
1. Go to this page and download the library: Download goletter/hyperf-docs library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
goletter / hyperf-docs example snippets
return [
'default' => env('DOCS_PLATFORM', 'google'),
// file(默认)| database(读库,缺项回落 file)
'config_source' => env('DOCS_CONFIG_SOURCE', 'file'),
'database' => [
'connection' => env('DOCS_DB_CONNECTION'), // null = 默认库
'table' => env('DOCS_CONFIG_TABLE', 'docs_platform_configs'),
'columns' => [
'platform' => 'platform',
'tenant_id' => 'tenant_id',
'payload' => 'config', // 可选 JSON 列;无列/为空则读 attributes
],
'attributes' => [
'client_id',
'client_secret',
'redirect_uri',
'app_id',
'app_secret',
'api_key',
'drive_root_folder',
'scopes',
],
],
// 自定义平台 class 映射(工厂可扩展 / 覆盖)
'platforms_map' => [
// 'my_docs' => \App\Docs\Platform\MyDocsPlatform::class,
],
'platforms' => [
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect_uri' => env('GOOGLE_REDIRECT_URI'),
'api_key' => env('GOOGLE_API_KEY'),
'drive_root_folder' => env('GOOGLE_DRIVE_ROOT_FOLDER_NAME', 'Goletter'),
'scopes' => [
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/spreadsheets',
],
],
'tencent' => [
'client_id' => env('TENCENT_DOCS_CLIENT_ID'),
'client_secret' => env('TENCENT_DOCS_CLIENT_SECRET'),
'redirect_uri' => env('TENCENT_DOCS_REDIRECT_URI'),
'drive_root_folder' => env('TENCENT_DOCS_DRIVE_ROOT_FOLDER_NAME', 'Goletter'),
// OAuth 文档要求 scope 固定为 all;实际权限以开放平台已开通权限为准
'scopes' => env('TENCENT_DOCS_SCOPES', 'all'),
],
'feishu' => [
'app_id' => env('FEISHU_APP_ID'),
'app_secret' => env('FEISHU_APP_SECRET'),
'redirect_uri' => env('FEISHU_REDIRECT_URI'),
'drive_root_folder' => env('FEISHU_DRIVE_ROOT_FOLDER_NAME', 'Goletter'),
'scopes' => env('FEISHU_SCOPES', 'sheets:spreadsheet drive:drive offline_access'),
],
],
];
use Goletter\Docs\DocsManager;
// 授权 URL / 换 token:凭证从库(或回落文件)读取
$url = $docs->auth('google')->getAuthUrl($state);
$token = $docs->auth('google')->fetchToken($code);
// 表格:用户 token 仍要自己传
$docs->sheets('google')->createSpreadsheet(
['access_token' => $userAccessToken],
'月报'
);
use Goletter\Docs\DocsContext;
DocsContext::setTenantId((string) $tenantId);
// 中间件示例:
// DocsContext::setTenantId((string) $request->getHeaderLine('X-Tenant-Id'));
use Goletter\Docs\DocsManager;
use Goletter\Docs\Contract\PlatformFactoryInterface;
use Hyperf\Di\Annotation\Inject;
#[Inject]
protected DocsManager $docs;
#[Inject]
protected PlatformFactoryInterface $factory;
$google = $this->docs->platform('google');
$tencent = $this->docs->platform('tencent');
$feishu = $this->docs->platform('feishu');
$authUrl = $this->docs->auth('google')->getAuthUrl();
$file = $this->docs->sheets('google')->createSpreadsheet(
['access_token' => $googleToken['access_token']],
'报表'
);
$file = $this->docs->sheets('tencent')->createSpreadsheet(
[
'access_token' => $tencentToken['access_token'],
'open_id' => $tencentToken['open_id'],
],
'报表'
);
$file = $this->docs->sheets('feishu')->createSpreadsheet(
['access_token' => $feishuToken['access_token']],
'报表'
);
$platform = $this->docs->make('google', [
'client_id' => $row['client_id'],
'client_secret' => $row['client_secret'],
'redirect_uri' => $row['redirect_uri'],
'drive_root_folder' => 'Goletter',
]);
$authUrl = $platform->auth()->getAuthUrl($state);
$token = $platform->auth()->fetchToken($code);
$file = $platform->sheets()->createSpreadsheet(
['access_token' => $token['access_token']],
'报表'
);
// 飞书
$feishu = $this->docs->make('feishu', [
'app_id' => $row['app_id'],
'app_secret' => $row['app_secret'],
'redirect_uri' => $row['redirect_uri'],
]);
// 等价
$this->factory->make('tencent', [
'client_id' => '...',
'client_secret' => '...',
'redirect_uri' => '...',
]);
'docs' => [
'handlers' => [
[
'class' => Monolog\Handler\RotatingFileHandler::class,
'constructor' => [
'filename' => BASE_PATH . '/runtime/logs/docs.log',
'level' => Monolog\Level::Info,
],
],
],
],
// config/autoload/docs.php
'platforms_map' => [
'my_docs' => \App\Docs\Platform\MyDocsPlatform::class,
],
// 或运行时注册
$this->factory->register('my_docs', MyDocsPlatform::class);
$this->factory->make('my_docs');
['id' => '...', 'title' => '...', 'url' => '...', 'raw' => /* 原始对象/数组 */]
use Goletter\Docs\DocsManager;
use Goletter\Docs\Platform\GooglePlatform;
/** @var GooglePlatform $platform */
$platform = $this->docs->platform('google');
$sheets = $platform->sheets();
$token = ['access_token' => $accessToken];
// 过期时:
// $token = $platform->refreshToken($refreshToken);
$spreadsheetId = '1f6KLBbJnsOMKMKUxkwH-Bfwm4YWkFA_RBibJbbLIq8M';
$gid = 649506568; // URL 里 #gid=
// 从分享链接解析 spreadsheetId / gid
// https://docs.google.com/spreadsheets/d/{id}/edit#gid=649506568
$parsed = $sheets->parseSpreadsheetUrl(
'https://docs.google.com/spreadsheets/d/1f6KLBbJnsOMKMKUxkwH-Bfwm4YWkFA_RBibJbbLIq8M/edit#gid=649506568'
);
// → spreadsheet_id / gid / url
// 或:Goletter\Docs\Support\SheetUrl::parseGoogle($url)
// 反解:SheetUrl::buildGoogle($spreadsheetId, $gid)
$title = $platform->getSheetTitleByGid($token, $spreadsheetId, $gid);
$range = $platform->rangeForGid($token, $spreadsheetId, $gid, 'A1:Z100');
// 只返回有内容的行,并裁掉行尾空单元格
// 第 4 个参数:每行至少几个非空单元格才保留(默认 1)
$rows = $sheets->readCells($token, $spreadsheetId, "gid:{$gid}");
$rows = $sheets->readCells($token, $spreadsheetId, "gid:{$gid}", 3);
// 按 F 列(也可用 0-based 下标,A=0)匹配
$matches = $sheets->findRows($token, $spreadsheetId, "gid:{$gid}", 'F', '333333333');
/*
[
[
'row' => 12,
'range' => "'Sheet'!A12:Z12",
'values' => [..., '333333333', true],
],
]
*/
$one = $matches[0] ?? null;
// null = 跳过该格(不会把保护列放进请求 range)
// '' = 清空(仍需编辑权限)
// true/false = 勾选框
$sheets->writeCells($token, $spreadsheetId, "gid:{$gid}!A1", [
[null, null, null, null, '名称', 'ID', null, true],
]);
// 更稳妥:直接写可编辑列
$sheets->writeCells($token, $spreadsheetId, "gid:{$gid}!F1", [['333333333']]);
$sheets->writeCells($token, $spreadsheetId, "gid:{$gid}!H1", [[true]]);
// 按行号编辑
$result = $sheets->updateRow(
$token,
$spreadsheetId,
"gid:{$gid}",
[null, null, null, null, '新名称', '333333333', null, true],
row: 12,
);
// 按列内容定位第一行再编辑(找不到返回 null)
$result = $sheets->updateRow(
$token,
$spreadsheetId,
"gid:{$gid}",
[null, null, null, null, '新名称', '333333333', null, false],
column: 'F',
match: '333333333',
);
/*
[
'row' => 12,
'range' => "'Sheet'!A12:Z12",
'values' => [..., '333333333', false], // 单行一维
]
*/
// 也可以 findRows + writeCells
$hit = $sheets->findRows($token, $spreadsheetId, "gid:{$gid}", 'F', '333333333')[0] ?? null;
if ($hit) {
$sheets->writeCells($token, $spreadsheetId, "gid:{$gid}!A{$hit['row']}", [
[null, null, null, null, '新名称', '333333333', null, true],
]);
}
// 按行号删除
$result = $sheets->deleteRow($token, $spreadsheetId, "gid:{$gid}", row: 12);
// ['row' => 12]
// 按列内容定位第一行再删除(找不到返回 null)
$result = $sheets->deleteRow(
$token,
$spreadsheetId,
"gid:{$gid}",
column: 'F',
match: '333333333',
);
// 单行(一维)
$result = $sheets->appendCells($token, $spreadsheetId, "gid:{$gid}", [
null, null, null, null, '名称', '333333333', null, true,
]);
/*
[
'row' => 12,
'range' => "'Sheet'!A12:Z12",
'values' => [..., '333333333', true], // 单行返回一维
]
*/
// 多行(二维);values 仍为二维
$result = $sheets->appendCells($token, $spreadsheetId, "gid:{$gid}", [
[null, null, null, null, '名称1', '111', null, true],
[null, null, null, null, '名称2', '222', null, true],
]);
// 单行 / 多行均可;始终插在第 1 行表头下面
$result = $sheets->insertAfterHeader($token, $spreadsheetId, "gid:{$gid}", [
[null, null, null, null, '名称1', '111', null, true],
[null, null, null, null, '名称2', '222', null, true],
]);
/*
[
'row' => 2,
'range' => "'Sheet'!A2:Z3",
'values' => [[...], [...]],
]
*/
// 单列:按 F 列匹配
$result = $sheets->upsertRows(
$token,
$spreadsheetId,
"gid:{$gid}",
[
[null, null, null, null, '名称1', '111', null, true],
[null, null, null, null, '名称2', '222', null, true],
],
column: 'F',
);
// 多列 AND:E + F 都相等才算同一行
$result = $sheets->upsertRows(
$token,
$spreadsheetId,
"gid:{$gid}",
[
[null, null, null, null, '名称1', '111', null, true],
[null, null, null, null, '名称2', '222', null, true],
],
column: ['E', 'F'],
);
// 表头占两行(第 1、2 行):数据从第 3 行起,新行插到第 3 行,第 1~2 行不参与匹配
$result = $sheets->upsertRows(
$token,
$spreadsheetId,
"gid:{$gid}",
[
[null, null, null, null, '名称1', '111', null, true],
],
column: 'F',
dataStartRow: 3,
);
/*
[
'updated' => [
['row' => 5, 'range' => "...", 'values' => [...]],
],
'inserted' => [ // 没有新行时为 null
'row' => 2,
'range' => "...",
'values' => [[...]],
],
]
*/
// 1) 表头下批量插入(默认 dataStartRow=2, position=prepend)
$sheets->insertBlock($token, $spreadsheetId, "gid:{$gid}!J:O", [
['2026/4/1', 2000, 1990, '水单', 1, '备注'],
['2026/4/2', 3000, 2980, '', 1, ''],
]);
// 2) 有则改、无则插到表头下(可多条件)
$sheets->upsertBlock(
$token,
$spreadsheetId,
"gid:{$gid}!A:E",
[
['2026/9/24', 10.5, 0.5, null, null],
['2026/9/23', 11, 0.6, null, null],
],
column: 'A', // 或多列 ['A', 'B'] / 相对下标 0
);
// 接到区块末尾(不插表头下)
$sheets->insertBlock(..., position: 'append');
$sheets->upsertBlock(..., column: 'P', position: 'append');
$sheets->upsertBlock(
$token,
$spreadsheetId,
"gid:{$gid}!J:O",
[['2026/4/1', 2000, 1990, '水单', 1, '备注']],
column: 'J',
dataStartRow: 4, // 跳过第 1~3 行表头/合计
position: 'prepend', // 插到第 4 行,只挤 J:O
);
$file = $sheets->createSpreadsheet($token, '报表');
$sheets->batchWrite($token, $spreadsheetId, [
['range' => "gid:{$gid}!A1", 'values' => [['a', 'b']]],
]);
$sheets->addSheet($token, $spreadsheetId, 'Sheet2');
$sheets->moveSpreadsheetToDateFolder($token, $spreadsheetId);
$sheets->shareSpreadsheetForAnyoneReader($token, $spreadsheetId);
use Goletter\Docs\Tencent\TencentAuth;
use Goletter\Docs\Tencent\TencentSheets;
use Hyperf\Di\Annotation\Inject;
#[Inject]
protected TencentAuth $auth;
#[Inject]
protected TencentSheets $sheets;
$authUrl = $this->auth->getAuthUrl(state: 'xyz'); // scope 固定为 all
$token = $this->auth->fetchToken($code);
// $token['access_token']、$token['refresh_token']、$token['open_id']
$file = $this->sheets->createSpreadsheet($accessToken, $openId, '报表');
$spreadsheetId = $file['ID'] ?? $file['id'];
// 从分享链接解析 spreadsheetId / sheetId(?tab=)
// https://docs.qq.com/sheet/DSEhEVGJuQ1dFQXhM?tab=BB08J2
// → spreadsheet_id=300000000$HHDTbnCWEAxL, sheet_id=BB08J2
$parsed = $this->docs->sheets('tencent')->parseSpreadsheetUrl(
'https://docs.qq.com/sheet/DSEhEVGJuQ1dFQXhM?tab=BB08J2'
);
// 或:Goletter\Docs\Support\SheetUrl::parseTencent($url)
// 读:spreadsheet v3(需 scope.sheet / scope.sheet.readonly)
$values = $this->sheets->readCells($accessToken, $openId, $spreadsheetId, 'A1:Z1000');
// 写:sheetbook v2
$this->sheets->writeCells($accessToken, $openId, $spreadsheetId, 'A1', [['姓名', '部门']]);
// 追加 / 按列查找(门面 sheets('tencent') 同样支持)
$sheets = $this->docs->sheets('tencent');
$tokenPayload = [
'access_token' => $accessToken,
'open_id' => $openId,
];
$sheets->appendCells($tokenPayload, $spreadsheetId, 'A1', [
'张三', '技术部',
]);
$sheets->findRows($tokenPayload, $spreadsheetId, 'A1:Z1000', 'A', '张三');
$sheets->updateRow($tokenPayload, $spreadsheetId, 'A1:Z1000', ['李四', '产品部'], column: 'A', match: '张三');
$sheets->deleteRow($tokenPayload, $spreadsheetId, 'A1:Z1000', column: 'A', match: '李四');
$this->sheets->moveSpreadsheetToDateFolder($accessToken, $openId, $spreadsheetId);
$this->sheets->shareSpreadsheetForAnyoneReader($accessToken, $openId, $spreadsheetId);
use Goletter\Docs\Feishu\FeishuAuth;
use Goletter\Docs\Feishu\FeishuSheets;
use Hyperf\Di\Annotation\Inject;
#[Inject]
protected FeishuAuth $auth;
#[Inject]
protected FeishuSheets $sheets;
$authUrl = $this->auth->getAuthUrl(state: 'xyz');
$token = $this->auth->fetchToken($code);
// $token['access_token']、$token['refresh_token'](须含 offline_access)
// 刷新后务必保存新的 refresh_token,旧值立即失效
$file = $this->docs->sheets('feishu')->createSpreadsheet(
['access_token' => $token['access_token']],
'报表'
);
$spreadsheetToken = $file['id'];
// 从分享链接解析 spreadsheetToken / sheetId(?sheet=)
// https://xxx.feishu.cn/sheets/shtcnXXXX?sheet=0b12
$parsed = $this->docs->sheets('feishu')->parseSpreadsheetUrl($url);
// 或:Goletter\Docs\Support\SheetUrl::parseFeishu($url)
$sheets = $this->docs->sheets('feishu');
$tokenPayload = ['access_token' => $token['access_token']];
// range:默认第一张表;也可用 sheetId / 标题
$values = $sheets->readCells($tokenPayload, $spreadsheetToken, 'A1:Z1000');
$sheets->writeCells($tokenPayload, $spreadsheetToken, 'A1', [['姓名', '部门']]);
$sheets->appendCells($tokenPayload, $spreadsheetToken, 'A1', ['张三', '技术部']);
$sheets->insertAfterHeader($tokenPayload, $spreadsheetToken, $parsed['sheet_id'] ?: 'A1', [
['李四', '产品部'],
]);
$sheets->upsertRows($tokenPayload, $spreadsheetToken, 'A1:Z1000', [
['张三', '技术部'],
], column: 'A');
$sheets->moveSpreadsheetToDateFolder($tokenPayload, $spreadsheetToken);
$sheets->shareSpreadsheetForAnyoneReader($tokenPayload, $spreadsheetToken);
bash
php bin/hyperf.php vendor:publish goletter/hyperf-docs