Сегодня вышел четвертый источник миграции в рамках нашего внедрения Tier-3. Вставьте токен API TinyURL Pro или Bulk, выберите целевой домен Elido и нажмите «Старт». Через 4-7 минут каждый alias TinyURL будет на вашем домене Elido с сохранением alias там, где не возникло конфликтов.
Эта публикация — инженерный отчет: что специфично для TinyURL, какой намеренный лимит мы внедрили и почему «миграция с бесплатного тарифа TinyURL» — это то, что мы не можем реализовать.
Проблема бесплатного тарифа#
У публичного TinyURL нет API, и никогда не было. Классический tinyurl.com/<slug>, который вы создаете без аккаунта, — это редирект типа «fire-and-forget»: пользователь создает его через форму на главной странице, получает slug, и этот slug никогда больше не появляется ни в одной панели управления аккаунтом. Нет списка по пользователям, так как нет привязки к пользователю.
Это общеизвестно, но стоит осветить это на лендинге /migrate-from/tinyurl, потому что поисковый запрос «migrate from TinyURL» не различает Pro и бесплатный тариф. Мы внедрили:
- Четкое уведомление «Только Pro/Bulk» в заголовке лендинга.
- Пункт FAQ, который направляет пользователей бесплатного тарифа на форму /docs/guides/bulk-create для массового сокращения через вставку списка ссылок.
- Шаг проверки токена в средстве запуска, который быстро выдает ошибку «этот токен не относится к тарифу Pro или Bulk», вместо того чтобы позволить процессу тихо упасть с ошибкой 401 в середине пагинации.
Логика: для любого другого источника миграции, который мы выпускаем, есть «счастливый путь» (happy path) для «каждого пользователя, который ищет его». TinyURL — исключение: пользователям бесплатного тарифа нужна другая ментальная модель, и мы должны задать это ожидание до того, как они что-либо вставят.
Формат REST API Pro/Bulk#
TinyURL Pro API прост: bearer-токен, JSON-ответы, 100 alias'ов на страницу. Пагинация использует параметр строки запроса page, который является 1-индексированным; ответ включает data.aliases (массив ссылок) и meta.has_more (сигнал продолжения).
const tinyurlPageSize = 100
page := 1
for {
resp, err := w.fetchPage(ctx, opts.Token, page)
if err != nil { /* mark failed */ return }
if len(resp.Data.Aliases) == 0 { break }
for _, alias := range resp.Data.Aliases { /* import */ }
if !resp.Meta.HasMore { break }
page++
}
Каждый alias несет url (длинный пункт назначения), alias (пользовательский slug или автоматически сгенерированный короткий код), description (опциональное поле TinyURL, которое мы сохраняем как заголовок ссылки Elido) и domain (TinyURL позволяет брендированные домены на тарифах Bulk).
Терминология — alias vs slug#
TinyURL называет их «alias'ами». Мы называем их «slug'ами». Это одно и то же — последовательность символов после хоста в URL редиректа. Миграция сохраняет alias 1:1 там, где целевой домен Elido не имеет конфликтов; если конфликт есть, применяется стандартная стратегия конфликтов: суффикс/пропуск/ошибка.
Мы рассматривали возможность переименования «slug» в «alias» в средстве запуска, чтобы соответствовать терминологии поставщика источника, но отвергли это по причинам согласованности. В любом другом интерфейсе Elido — списке ссылок, API, SDK, дашборде — используется «slug». Внедрение асимметрии терминологии в один лаунчер сделало бы опыт после импорта запутанным.
Лаунчер выводит однострочную метку «TinyURL называет их alias'ами» над радиокнопкой стратегии конфликтов, чтобы пользователи, ищущие «alias» на странице рецепта, нашли нужный элемент управления, не читая каждое слово.
Брендированные домены и передача DNS#
Тарифы TinyURL Bulk поддерживают брендированные домены — ваше собственное имя хоста, направляемое через инфраструктуру TinyURL. Когда вы мигрируете в Elido, slug импортируется чисто, а со стороны DNS это одно изменение CNAME.
Интересный случай: «У меня есть брендированный домен на TinyURL Bulk, и я хочу сохранить то же имя хоста после миграции». Мы обрабатываем это так же, как миграцию с Short.io:
- Миграция завершена. Импортированные ссылки по умолчанию находятся на
s.elido.me/<alias>(или на вашем существующем пользовательском домене Elido). - Вы добавляете брендированный домен TinyURL в качестве пользовательского домена Elido через /docs/guides/custom-domains.
- Вы направляете CNAME на Elido. On-demand TLS в Caddy выдает сертификат при первом запросе;
domain-managerявляется источником истины для разрешенного списка, поэтому неавторизованные имена хостов отклоняются. - Поверхность TinyURL перестает обслуживать это имя хоста; поверхность Elido берет управление на себя.
Вы можете поддерживать обе поверхности параллельно до окончания вашей подписки TinyURL, затем переход — это просто позволить имени хоста TinyURL истечь. Никакой срочности, никакого риска в день перехода.
Что мы не мигрируем#
Историю кликов. Аналитика TinyURL Pro/Bulk — это отдельные эндпоинты отчетов, которые не структурированы для экспорта. Тариф Bulk показывает количество кликов по ссылкам в дашборде, но не предоставляет их через API, подходящий для миграции; новые клики попадают в аналитику Elido с момента перехода.
Стилизацию QR и шаблоны UTM тарифа Bulk. То же самое, что и с любым другим источником миграции — slug импортируется, окружающий слой представления перестраивается внутри Elido. Помечено тегом imported:tinyurl для последующей массовой обработки через кампании.
Ссылки бесплатного тарифа TinyURL. Как обсуждалось выше, у публичного TinyURL нет API. Смягчение последствий — это форма массового создания, а не задание миграции.
Обработка токенов#
Та же семантика однократного использования, что и у Bitly, Rebrandly и Short.io:
bgCtx := context.WithoutCancel(r.Context())
go h.tinyurl.Run(bgCtx, job.ID, imports.TinyURLJobOptions{
Token: req.Token,
})
source_token_id остается NULL. Токен живет в памяти процесса api-core во время выполнения воркера и удаляется по завершении. Никакой персистентности, никакой строки service_tokens, никакого шифрования конверта ADR-0036 — они предназначены для интеграций Tier-2, где пользователю нужны повторяющиеся вызовы поставщика.
Шаг проверки токена при запуске задания обращается к эндпоинту TinyURL /account/domains — дешевый вызов, возвращает список доменов, которые видит токен. Если он выдает 401, мы быстро выдаем ошибку «токен недействителен или не относится к тарифу Pro/Bulk», вместо того чтобы заставлять пользователя ждать две минуты для получения 401 в середине пагинации и менее полезного сообщения об ошибке.
Разрешение конфликтов#
Идентично любому другому поставщику миграции — суффикс перебирает myalias-2, myalias-3… при конфликте; пропуск оставляет существующую ссылку Elido в покое и логирует исходную строку; отмена прерывает работу при первом же конфликте.
func (w *TinyURLWorker) resolveSlug(ctx context.Context, domainID int64, desired, strategy string) (string, error) {
if _, err := w.links.GetByDomainSlug(ctx, domainID, desired); err != nil {
if errors.Is(err, pgx.ErrNoRows) { return desired, nil }
return "", fmt.Errorf("slug lookup: %w", err)
}
// suffix/skip/fail branching identical to bitly.go
}
Поиск — это одно индексированное чтение на строку. Мы платим за дополнительное чтение, но получаем детерминированные переборы суффиксов и более понятные сообщения об ошибках, чем при попытке отловить нарушения уникальности.
Контракт воркера#
MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000. Общие для всех пяти поставщиков миграции. Эти константы — контракт, который предполагает UI опроса дашборда.
Аккаунт TinyURL Pro на 2 000 alias'ов обращается к API 20 раз и завершается за 3–5 минут. Аккаунт Bulk на 20 000 alias'ов требует 200 круговых рейсов и завершается за 15–20 минут. Свыше 50 000 alias'ов воркер жестко завершается с инструкцией написать на [email protected] для чанковой миграции; путь чанковой миграции в v1 доступен только в индивидуальном порядке.
Возобновляемость и проблема развертывания#
Тот же компромисс, что и в первых трех миграциях. Воркер работает в процессе; развертывание в середине импорта убивает горутину. Крон-задача «stuck-sweep» переводит любую строку running без прогресса за 30 минут в состояние failed. Повторный запуск идемпотентен при выборе стратегии суффикса или пропуска.
Для аккаунтов более чем на 10 000 alias'ов возобновляемость была бы полезна — мы бы записывали курсор page TinyURL в import_jobs.source_filter и возобновляли работу с последней завершенной страницы. Четыре других поставщика миграции выиграют от этого же изменения, как только мы его выпустим; дизайн является общим.
CSV-резерв#
Для пользователей тарифов Bulk с экспортированным CSV, у которых больше нет активного токена API, мы запускаем одноразовые CSV-задания из входящих — пишите на [email protected]. Мы не стали выпускать форму самообслуживания для загрузки CSV, потому что REST-путь покрывает обычные случаи, а CSV-путь требует «массажа» схемы под каждый конкретный аккаунт, что лучше делать вручную, чем хрупким обобщенным парсером.
Что дальше#
Остался еще один поставщик:
- Dub.co —
GET /api/links?projectSlug=…&limit=100. Папки преобразуются в теги. Самый чистый API из пяти.
После Dub внедрение Tier-3 будет завершено. Пять миграций, пять инженерных постов в блоге, одна общая инфраструктура воркера, один общий UI опроса дашборда.
Если вы воздерживались, потому что миграция с TinyURL была недокументирована, теперь она задокументирована. Попробуйте — от токена Pro/Bulk до последней импортированной ссылки менее чем за семь минут для типичных аккаунтов.