Мне поставили задачу: сформировать документ через Adobe API. Требований было минимум - главное сформировать документ из шаблона. Шаблон я мог отформатировать в нужный мне формат без проблем. Но сам формат, как его использовать и как это связать с API я не знал. Поэтому нырнул в документацию.
После пары часов изучения, я отчаялся. Информации было море, но нужной информации не нашел. Но зато нашел несколько вариантов документации, пару статей и несколько видео-уроков. Всё это не приблизило меня к цели ни на шаг.
Но спустя несколько дней, огромное количество нервов и желания сдаться, я всё-таки решил задачу. И буду рад поделиться решением тут.
Adobe Document Generation API
Первая задача: какой из Adobe сервисов нужен.
Adobe Document Generation API. Он отвечает за генерацию, сжатие, конвертирование и иную работу с документами. Но нас интересует только генерация.
Чтобы его использовать, необходимо выполнить следующие шаги:
Создать приложение
Получить токен доступа (access token)
Подготовить шаблон
Загрузить шаблон
Сформировать итоговый документ
Создаем приложение
Переходим по ссылке https://developer.adobe.com/console/projects, затем:
Нажимаем "Create new Project"
Нажимаем "Add to project" - выбираем API и ищем там PDF Services API
Выбираем автоматическую генерацию приватных ключей, жмем "Continue" кучу раз
Ждем пока Adobe сгенерирует доступы и предложит скачать их
Переходим в приложение во вкладку "Credentials" где будут данные для генерации JWT.
Если Adobe не прислал Client Secret, пересоздаем его тут же и сохраняем себе.
Получаем токен доступа (access token)
Первым делом мы создаем JWT строку, параметры которой указаны тут: https://developer.adobe.com/developer-console/docs/guides/authentication/JWT/
<?php
use Firebase\JWT\JWT;
$organizationID = 'Organization ID с вкладки Credentials';
$accountID = 'Technical Account ID с вкладки Credentials';
$clientID = 'Client ID с вкладки Credentials';
$privateKey = file_get_contents('путь до файла private.key из архива');
$payload = [
'exp' => time() + 60 * 60 * 2,
'iss' => $organizationID,
'sub' => $accountID,
'aud' => 'https://ims-na1.adobelogin.com/c/' . $clientID,
'https://ims-na1.adobelogin.com/s/ent_documentcloud_sdk' => true
];
// Используется firebase/php-jwt
$jwt = JWT::encode($payload, $privateKey, 'RS256');
Далее запрашиваем access token. Используем запрос, который я нашел в официальной коллекции Adobe в Postman(Adobe Target Admin APIs/Exchange JWT for Access token):
<?php
// JWT получен выше
$clientID = 'Client ID с вкладки Credentials';
$clientSecret = 'Client Secret с вкладки Credentials';
$query = [
'client_id' => $clientID,
'client_secret' => $clientSecret,
'jwt_token' => $jwt,
];
// Используется фасад Http из Laravel. Но суть его в формировании обычного POST запроса. Да, без тела. Так надо
$response = Http::post('https://ims-na1.adobelogin.com/ims/exchange/jwt?' . http_build_query($query));
$token = $response->json('access_token');
В ответе будет поле access_token с нашим токеном. Либо ошибка и её описание.
Подготавливаем шаблон
Шаблон заполняется JSON данными. А в самом документе необходимо указать ключи этого самого JSON.
Пример JSON:
{
"name": "Dmitry",
"meta":{
"age": 23
}
}
В шаблоне можно указать ключи {{name}}
и {{meta.age}}
Загружаем шаблон
Adobe не хранит у себя файлы. Он использует сервера Amazon.
Поэтому сперва получаем через Adobe доступ к Amazon, а затем загружаем наш шаблон.
Получаем доступ к Amazon:
<?php
// access token был получен выше
$token = '';
$clientID = 'Client ID с вкладки Credentials';
// У docx mime тип "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
$mime = 'mime тип шаблона. Обычно это docx'
// Используется фасад Http Laravel:
// - POST запрос
// - Тело запроса raw application/json
// - Авторизация Bearer с access token
// - Доп. заголовок "x-api-key" с вашим ClientID
$response = Http
::withToken($token)
->asJson()
->withHeaders([
'x-api-key' => $clientID
])
->post('https://pdf-services.adobe.io/assets', [
"mediaType" => $mime
]);
$assetID = $response->json('assetID');
$uploadUri = $response->json('uploadUri');
// В ответе будет assetID и uploadUri. Второй нужен для загрузки файла
Загружаем шаблон:
<?php
// $uploadUri был получен выше
$mime = 'mime тип загружаемого шаблона'
// Тут привычный всем Guzzle:
// - PUT запрос
// - Тело запроса файл в байтах
// - Доп. заголовок "Content-Type" с вашим mime типом загружаемого файла
$request = new \GuzzleHttp\Psr7\Request(
'PUT',
$uploadUri,
['Content-Type' => $mime],
file_get_contents('путь до шаблона')
);
$client = new Client();
$response = $client->send($request);
// Если не 200 код, то явно была ошибка
if ($response->getStatusCode() != 200) {
// Обработка ошибки, иначе использовать assetID полученный выше
}
Шаблон загружен
Формируем документ из шаблона
Отправляем запрос на формирование документа:
<?php
// assetID был получен выше
$clientID = 'Client ID с вкладки Credentials';
// access token был получен выше
$token = '';
$data = [
"name" => "Dmitry",
"meta" => ["age" => 23]
];
// Используется фасад Http Laravel:
// - POST запрос
// - Тело запроса raw application/json
// - Авторизация Bearer с access token
// - Доп. заголовок "x-api-key" с вашим ClientID
$response = Http
::withToken($token)
->asJson()
->withHeaders([
'x-api-key' => $clientID
])
->post('https://pdf-services.adobe.io/operation/documentgeneration', [
"assetID" => $assetID,
"outputFormat" => "pdf",
"jsonDataForMerge" => $data
]);
// Если статус не 201, то произошла ошибка
if ($response->status() != 201) {
// Обработка ошибки
}
// Необходимо забрать заголовок x-request-id
$requestID = $response->header('x-request-id')
После отправки запроса, необходимо проверить статус запроса:
<?php
// x-request-id был получен выше
$requestID = '';
// access token был получен выше
$token = '';
$clientID = 'Client ID с вкладки Credentials';
// Используется фасад Http Laravel:
// - GET запрос
// - Авторизация Bearer с access token
// - Доп. заголовок "x-api-key" с вашим ClientID
$response = Http
::withHeaders([
'x-api-key' => $clientID
])
->withToken($token)
->get("https://pdf-services.adobe.io/operation/documentgeneration/{$requestID}/status");
$status = $response->json('status');
// Если статус не done, то либо ошибка, либо ещё формируется
if ($status != 'done') {
// Обработка ошибки
}
// Если всё успешно, то можно получить ID документа и ссылку на скачивание
$assetID = $response->json('asset.assetID');
$downloadUri = $response->json('asset.downloadUri');
Документ сформирован и доступен по ссылке $downloadUri
Neverfan
Здравствуйте, спасибо за статью. Интересует, почему поставили задачу именно так? Почему решили реализовать именно через Adobe API, а не классическим способом через wkhtmltopdf или node.js (мы используем обертку spatie/browsershot). В шаблоне были какие-то чертежи куда нужно добавить подписи?
dmtrbskkv Автор
На то были 2 причины:
у клиента была подписка на Adobe и он работал там
я работаю на PHP
Но тут стоит добавить, что это первая половина работы - генерация документа. Вторая - отправка документа на подпись через Adobe Sign API. Про вторую часть я потихоньку тоже пишу статью, чтобы хоть немного облегчить жизнь тем кто начнет с этим всем работать
А сам по себе шаблон был документом, в котором необходимо заполнить детали сделки и данные клиента. И было важно соблюсти формат в котором был прислан документ
rsmike
Есть вариант проще - темплейты в ворде, замены в PHPOffice, конвертация через Gutenberg
dmtrbskkv Автор
Да, вполне. Может в следующий раз предложу такой вариант клиенту
Neverfan
Мы генерируем PDF для законотельных актов всех видов, думаю темплейты нам не помогут, но про такой способ не слышал, поизучаю информацию