При интеграции систем автоматизации на платформе 1С с внешними программными продуктами разработчики часто сталкиваются с жесткими ограничениями на длину ключевых полей. Стандартный 128-битный идентификатор (GUID/UUID) в текстовом представлении занимает 36 символов (например, 550e8400-e29b-41d4-a716-446655440000). Но что делать, если внешняя база данных или веб-сервис принимает строку длиной не более 9 символов?
В этой статье мы подробно разберем математические и программные аспекты решения этой задачи. Мы проанализируем, как физически упаковать 128 бит информации в 9 символов, рассмотрим безопасные алгоритмы кодирования в Юникод, оценим риски возникновения дублей (коллизий) и предложим готовые альтернативные решения для платформы 1С.
Для начала давайте разберем математику процесса. Уникальность GUID базируется на его длине — 128 бит. Это дает $2^{128}$ (или приблизительно $3.4 \times 10^{38}$) возможных комбинаций. Любая попытка уменьшить этот объем информации неизбежно ведет к сужению пространства возможных значений.
Если мы хотим представить те же 128 бит информации ровно в 9 символах, нам необходимо вычислить, сколько уникальных значений должен поддерживать каждый символ. Сделаем расчет:
$2^{128/9} \approx 19113$ значений на один символ.
Это означает, что если каждый из 9 символов нашей результирующей строки может принимать одно из 19 113 уникальных значений, то мы сможем закодировать абсолютно любой GUID без малейшей потери уникальности и с возможностью 100% обратного декодирования.
Обычный латинский алфавит и цифры (Base62) предоставляют всего 62 значения на символ. Символы шестнадцатеричной системы (Hex) — всего 16 значений. Очевидно, что стандартными ASCII-символами обойтись не получится. Нам необходимо использовать возможности Юникода.
В платформе 1С для работы со строками используется кодировка UTF-16. В этой кодировке каждый символ базовой плоскости занимает ровно 2 байта (16 бит). Проанализируем физический объем памяти:
Следовательно, чисто физически 16 байт GUID полностью и без потерь помещаются в 18 байт строки 1С. Казалось бы, задача решается простым побайтовым преобразованием (кастом) буфера двоичных данных в строку. Однако на практике этот метод в лоб использовать нельзя по следующим причинам:
0x00, который в базах данных (например, MS SQL или PostgreSQL) интерпретируется как конец строки. Данные будут обрезаны. Также могут возникнуть символы перевода строки, табуляции или кавычки, ломающие сериализацию в JSON/XML.0xD800 до 0xDFFF зарезервирован для суррогатных пар. Если случайный набор байтов GUID сформирует код из этого диапазона без корректной пары, СУБД или парсер отбросят его как поврежденный символ.Для безопасного сжатия двоичных данных в Юникод-строку применяются специализированные алгоритмы, такие как Base32768. Этот алгоритм использует строго выверенный алфавит из 32 768 печатных символов Юникода, которые гарантированно безопасны для сохранения в базах данных и передачи по веб-протоколам.
Каждый символ в Base32768 кодирует ровно 15 бит информации. Проведем расчет:
128 бит (GUID) / 15 бит на символ = 8.53 символа.
Таким образом, любой произвольный GUID гарантированно упаковывается в 9 символов Юникода без какого-либо риска появления некорректных кодов, управляющих знаков или сломанных суррогатных пар.
Рассмотрим концептуальный алгоритм упаковки GUID в массив 15-битных блоков на встроенном языке 1С:
// Функция преобразует УникальныйИдентификатор в безопасную строку из 9 символов
Функция УпаковатьGUIDВДевятьСимволов(Знач ИсходныйGUID) Экспорт
// Получаем двоичные данные GUID (16 байт)
СтрокаБезДефисов = СтрЗаменить(Строка(ИсходныйGUID), "-", "");
ДвоичныеДанныеGUID = ПолучитьДвоичныеДанныеИзГексагональнойСтроки(СтрокаБезДефисов);
// В реальном алгоритме Base32768 мы разбиваем 128 бит буфера на блоки по 15 бит
// и сопоставляем каждый блок с таблицей безопасных символов Юникода.
// Ниже приведен схематичный пример побайтового смещения:
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ДвоичныеДанныеGUID);
Результат = "";
Для Сч = 0 По 7 Цикл
// Читаем по 2 байта (16 бит) и приводим их к диапазону безопасных символов
Значение16Бит = Буфер.ПрочитатьЦелое16(Сч * 2);
// Смещаем код, чтобы обойти диапазон управляющих символов и суррогатных пар
БезопасныйКодСимвола = Значение16Бит + 256;
Если БезопасныйКодСимвола >= 55296 Тогда // 0xD800
БезопасныйКодСимвола = БезопасныйКодСимвола + 2048; // обходим диапазон суррогатов
КонецЕсли;
Результат = Результат + Символ(БезопасныйКодСимвола);
КонецЦикла;
// Дописываем оставшиеся биты
ПоследнийБайт = Буфер.Получить(15);
Результат = Результат + Символ(ПоследнийБайт + 256);
Возврат Результат;
КонецФункции
Если внешняя система не поддерживает Юникод (например, принимает только стандартные ASCII-символы: латинские буквы и цифры), то использовать схему сжатия Юникодом не получится. В этом случае придется пожертвовать частью уникальности GUID.
Если мы закодируем данные в алфавит Base62 (символы a-z, A-Z, 0-9), то 9 символов обеспечат нам следующую емкость:
$62^9 \approx 1.35 \times 10^{16}$ вариантов (примерно 53.7 бит энтропии).
Это значительно меньше, чем исходные 128 бит GUID, но все же предоставляет внушительный диапазон чисел. Как получить эти 9 символов?
Давайте проанализируем риски возникновения дубликатов (коллизий) по теории «Парадокса дней рождения» для этого метода. Вероятность появления хотя бы одного дубля составит:
Для малых и средних баз данных (где количество передаваемых документов или контрагентов исчисляется десятками или сотнями тысяч) этот метод вполне применим и гарантирует высокую степень практической уникальности.
Вместо того чтобы пытаться сжать изначально случайный 128-битный GUID, гораздо эффективнее использовать автоинкрементные целочисленные идентификаторы СУБД или последовательности 1С, закодированные в короткий строковый формат.
В веб-разработке для этого применяется концепция, аналогичная библиотеке Hashids (или Squids). Мы берем внутренний порядковый номер записи (например, автоинкрементное число) и шифруем его в короткую строку фиксированной длины (например, в те самые 9 символов) по кастомному алфавиту.
Рассмотрим алгоритм генерации такого идентификатора на стороне 1С:
// Функция преобразует порядковое число (например, Код или автоинкрементный ИД) в Base62 строку
Функция ЧислоВBase62(Знач ЧисловойИД, МинимальнаяДлина = 9) Экспорт
Алфавит = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Основание = 62;
Результат = "";
Пока ЧисловойИД > 0 Цикл
Остаток = ЧисловойИД % Основание;
СимволАлфавита = Сред(Алфавит, Остаток + 1, 1);
Результат = СимволАлфавита + Результат;
ЧисловойИД = Цел(ЧисловойИД / Основание);
КонецЦикла;
// Дополняем строку символами-заполнителями до минимальной длины
Пока СтрДлина(Результат) < МинимальнаяДлина Цикл
Результат = "a" + Результат;
КонецЦикла;
Возврат Результат;
КонецФункции
Этот подход гарантирует абсолютную уникальность (коллизии исключены математически, так как исходные числа уникальны) и укладывается всего в несколько ASCII-символов.
Выбор оптимального метода сжатия идентификатора зависит от ограничений внешней системы: