Нередко веб-сайты перерастают свою первоначальную архитектуру по мере того, как они развиваются и адаптируются к потребностям своих пользователей. Так было с одним из моих клиентов, который изначально использовал комбинацию плагинов, предназначенных для создания галерей изображений, для создания собственной системы продуктов. С тысячами продуктов установка начала показывать свой возраст, вызывая проблемы с производительностью и надежностью, но, что наиболее важно, ни один из их продуктов не имел выделенных страниц с собственными URL-адресами, вместо этого он был больше похож на веб-приложение. Это было ужасно для пользовательского опыта и SEO — и поэтому самым большим преимуществом было то, что у каждого продукта woocommerce были свои собственные страницы и хорошие канонические URL-адреса.
Я сообщил им, что уверен, что можно будет программно перенести их продукты на Woocommerce, сэкономив сотни часов ручного повторного входа.
Первоначальный подход: собственный серверный плагин
Логичным первым шагом было разработать подход на чистом PHP, разработав специальный серверный плагин для обработки миграции. Этот плагин был разработан для анализа и объединения разрозненных разрозненных данных для каждого продукта с целью беспрепятственного импорта всех данных в Woocommerce.
Однако в процессе случилась заминка. Один из исходных плагинов хранил данные в странном закодированном формате, что приводило к некоторым проблемам с несогласованностью во время импорта. Имея в виду жесткие временные ограничения, я понял, что вместо реинжиниринга необходим другой подход, чтобы уложиться в сроки проекта.
Разворот: очистка внешнего интерфейса
Я реализовал решение для перебора всех страниц категорий, создав iframe для каждой в качестве средства для очистки продуктов. Я выполнял парсинг последовательно, так как не хотел сталкиваться с проблемами тайм-аута или сокета, так как каждый iframe, очевидно, был бы довольно емким, делая запросы на целую страницу. Такова жизнь.
let finalResult = { "categories": [] }; let anchors = Array.from($(".et_pb_module_header > a")); function processIframe(index) { if (index >= anchors.length) { // All iframes processed, show the download modal... let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(finalResult)); let downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", "gallery.json"); downloadAnchorNode.innerHTML = "Download JSON"; downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", "gallery.json"); downloadAnchorNode.innerHTML = "Download JSON"; downloadAnchorNode.style.color = "blue"; downloadAnchorNode.style.padding = "10px"; downloadAnchorNode.style.fontFamily = "Arial, sans-serif"; downloadAnchorNode.style.fontSize = "16px"; downloadAnchorNode.style.marginRight = "20px"; let closeAnchorNode = document.createElement('a'); closeAnchorNode.innerHTML = "Close"; closeAnchorNode.onclick = function() { modal.style.display = "none"; }; closeAnchorNode.style.color = "red"; closeAnchorNode.style.padding = "10px"; closeAnchorNode.style.fontFamily = "Arial, sans-serif"; closeAnchorNode.style.fontSize = "16px"; let modalContent = document.createElement('div'); modalContent.appendChild(downloadAnchorNode); modalContent.appendChild(closeAnchorNode); modalContent.style.padding = "20px"; modalContent.style.textAlign = "center"; let modal = document.createElement('div'); modal.appendChild(modalContent); modal.style.display = "block"; modal.style.width = "300px"; modal.style.height = "150px"; modal.style.margin = "15% auto"; modal.style.border = "1px solid #888"; modal.style.boxShadow = "0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19)"; modal.style.background = "#fefefe"; modal.style.zIndex = "9999"; modal.style.position = "absolute"; modal.style.left = "0"; modal.style.right = "0"; document.body.prepend(modal); } else { let iframe = document.createElement('iframe'); iframe.src = anchors[index].href; iframe.style.display = "none"; document.body.appendChild(iframe); iframe.onload = function() { let iframeWindow = iframe.contentWindow; let iframeDocument = iframeWindow.document; let jsonGallery = { "category_title": iframeWindow.$("h1").text(), "woocommerce_products": [] }; // Run the original function within the iframe iframeWindow.$('.modula-item').each(function() { let titleElement = iframeWindow.$(this).find(".jtg-title"); let siblingElements = iframeWindow.$(this).find("a[data-caption]"); let woocommerceProduct = { "title": titleElement.text() || 'N/A', "description": 'N/A', "featured_image": 'N/A', "filter_classes": [] }; if (siblingElements.length > 0) { let description = siblingElements.attr('data-caption'); let imageId = siblingElements.attr('data-image-id'); woocommerceProduct.description = description || 'N/A'; woocommerceProduct.featured_image = imageId || 'N/A'; } let classList = iframeWindow.$(this).attr('class').split(/\s+/); iframeWindow.$.each(classList, function(index, item) { if (item.startsWith('jtg-filter-')) { woocommerceProduct.filter_classes.push(item); } }); jsonGallery.woocommerce_products.push(woocommerceProduct); }); finalResult.categories.push(jsonGallery); // Remove the iframe when we're done document.body.removeChild(iframe); // Move to next iframe processIframe(index + 1); }; } } // Start processing processIframe(0);
При запуске скрипта на странице верхнего уровня магазина он начинает с инициализации пустого объекта (finalResult
) для хранения очищенных данных. Затем он собирает все ссылки категорий в массив (anchors
).
Функция processIframe
принимает в качестве параметра индекс, представляющий текущую категорию для обработки. Если все категории были обработаны, он компилирует JSON и представляет ссылку для скачивания во всплывающем модальном окне.
Если еще есть категории для обработки, функция создает невидимый iframe для категории, соскребая данные о продукте при загрузке iframe. Название, описание, избранное изображение и теги каждого продукта (называемые здесь «фильтрами») извлекаются, затем сохраняются в объекте woocommerceProduct
, который помещается в массив woocommerce_products
категории. Это продолжается до тех пор, пока не будут обработаны все продукты из всех категорий.
Наконец, скрипт упаковывает все очищенные данные в один файл JSON, готовый для загрузки и импорта в Woocommerce.
После успешной миграции мы смогли еще больше повысить производительность, используя переходные процессы и реализовав несколько уровней кэширования на стороне сервера, о счастливом конце и мечтать нельзя!