Как сохранить большой двоичный объект (ответ AJAX) в именованный файл (тип JSON) на моем сервере, а не на пользовательском компьютере

Я прочитал почти все темы о создании URL-адреса из (или загрузки) BLOB-объекта на компьютере, однако мне нужно что-то другое:

(1) сохранить большой двоичный объект на собственном сервере в файл JSON, затем

(2) оптимизировать способ обновления базы данных с использованием данных, хранящихся в JSON.

Я пробовал все варианты кода ниже без успеха

var blob = new Blob(response, { type: contentType });
var file = new File([blob], filename, {type: contentType, lastModified: Date.now()});

где ответ был parsedJSON из приведенного ниже кода.

Было бы лучше вместо этого иметь файл с пустым содержимым на сервере, открыть его и обновить из большого двоичного объекта (затем закрыть его)?

Моя цель - загрузить содержимое ответа из запроса AJAX (из API) на мой сервер (а не на какой-либо клиентский компьютер в браузере), а затем обновить соответствующие значения в таблице базы данных.

Вариант использования следующий: я получаю прайс-лист (около 40 000 позиций) из API с помощью cron, затем мне нужно обновить цены на продукты в базе данных на моем сервере.

Давайте посмотрим на код ниже:

<div id="app">
    <p style="text-align: center; margin-top: 50px;">
        <i class="fa fa-spinner fa-spin" style="font-size: 15px;"></i><br>
        please wait to load the list of items here...
    </p>
</div>

<script>
var settings = {
  "url": "https://www.notimportant/etc/",
  "method": "GET",
  "timeout": 0,
  "headers": {
    "GUID": "something"
  },
};


$.ajax(settings).done(function(response) {
  var parsedJSON = JSON.parse(response);
  
                                                  console.log(parsedJSON[0]); /* <= to see the structure of each object in the response array */
                                                  console.log('total items: ' + parsedJSON.length);
  
   var template = `
<p>List of items</p>
{{#each this}}
<div>{{plusOne @index}} - Code: {{ProductCode}} - Name: {{ProductName}} - Price: {{ProductPrice}}</div>
{{/each}}
`;

Handlebars.registerHelper('plusOne', function(index) {
    index++;
    return index;
});
  var show = Handlebars.compile(template);
  $('#app').html(show(parsedJSON));
});
        </script>

Пока все хорошо, я мог проверить с помощью Handlebars JS результаты в браузере (и также увидеть в консоли структуру объекта, полученную от API). Однако моя цель — использовать каждый объект из массива parsedJSON для обновления значений в таблице. Я мог бы сделать что-то вроде

for(i = 0; i < parsedJSON.length; i++) { // use here $.post to send ProductCode, ProductName and ProductPrice to a php file where I execute a query to update each product into the database

var ProductCode = parsedJSON[i].ProductCode;
var ProductName = parsedJSON[i].ProductName;
var ProductPrice = parsedJSON[i].ProductPrice;

  $.post("update_db.php", {code: ProductCode, name: ProductName, price: ProductPrice}, function(data, status){
    console.log("Product " + ProductCode + " update = " + status);
  });
}

alert("all products' update was finished");

однако это создаст ~ 40 000 одиночных запросов в мою базу данных (что совсем не нормально).

Вместо этого я бы предпочел сохранить массив на сервере (чтобы сохранить большой двоичный объект ответа, создайте из него файл, назовите его myJSONfile.json, затем в php используйте $myfileaddress = $base + "myJSONfile.json", прочитать его с помощью $myfile = file_get_contents($myfileaddress), затем $myJson = json_decode($myfile, true), а затем обновить базу данных с помощью foreach($array as $item) {// update each row in products table in database и т. д. }

Однако я сомневаюсь, что смогу запустить множественный запрос с 40 000 компонентов (или подготовленный оператор такого большого размера, даже если я значительно увеличу разрешенную память - я бы предпочел этого не делать). Если я ошибаюсь, объясните мне, почему.

На данный момент мой код просто отправляет запросы один за другим, что я бы не стал делать (мне лучше использовать только php или только JavaScript, чтобы создать одно соединение с БД, а не много на данный момент) .

На самом деле я получаю почти мгновенный ответ - предупреждение обновление всех продуктов было завершено для полного списка продуктов, но затем ответные сообщения php также продолжают поступать в консоль в течение довольно долгого времени об успешном обновлении. каждую строку (разговор о нескольких минутах), чего я не ожидал.

Я бы использовал что-то элегантное, скажем, использовать 500 запросов в одной транзакции (и сделать все обновление всего за 80 шагов вместо полных 40 000 простых запросов), но не уверен, как все правильно настроить.

Моя точка зрения на загрузку полного JSON вместо обновления каждого продукта через отдельный запрос API (плюс обновление) заключается в том, чтобы запросить внешний сервер только один раз (используя cron), а не много раз, и выполнить дальнейшую обработку данных в моем собственном сервер вместо этого. В реальной ситуации каждый товар поступает из API с примерно 20 параметрами, а не только с 3 (Код, Название и Цена), но это не имеет отношения к рассматриваемым вопросам. (Кроме того, извините за некоторые небольшие небрежности в коде, показанном в отдельных фрагментах, но я быстро набирал вместо каких-либо копипастов из производственных файлов - я сейчас не пишу с компьютера, на котором я работаю над рассматриваемым проектом).

Окончательное редактирование

Наконец, я написал приведенный ниже более компактный код, который отлично работает (подготовленный оператор с UPDATE). Вместо того, чтобы публиковать его как отдельный ответ, я вставлю его здесь:

<?php
$servername = "localhost";
$username = "someuser";
$password = "somepassw";
$dbname = "somedb";

// requiring data from API

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://www.notimportant/etc/",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS =>"{\"name\": null, \"stores\": null, \"products\": []}",
  CURLOPT_HTTPHEADER => array(
    "GUID: something",
    "Content-Type: application/json"
  ),
));

$response = curl_exec($curl);

curl_close($curl);

                                                        // echo $response;
$data = json_decode($response);
echo "Product count: " . count($data) . "<br>\n";
echo "Preparing to save products to database... <br>\n";

// end receiving data from API



// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
  die("Connection failed: " . $conn->connect_error);
}

// prepare and bind
$stmt = $conn->prepare("UPDATE `my_table` SET `price` = ?, `stock` = ? WHERE `my_table`.`product_id` = ?"); 
$stmt->bind_param("dii", $price, $stock, $id);

// set parameters and execute
foreach($data as $productInfo) {
    $price = $productInfo->RetailPrice;
    $stock = $productInfo->Stock;
    $id = $productInfo->ProductId;
    $stmt->execute();
    }

echo "Update was ok";

$stmt->close();
$conn->close();

?>

Предложения @Bravemaster были полезны, теперь код выполняется менее чем за 10 секунд, так что на данный момент это приемлемое решение. Пункт (2) довольно решен, а (1) кажется бесполезным (поскольку нет смысла использовать и file_put_contents, и file_get_contents). Я также голосую за вклад Bravemaster как за принятый ответ - за его любезный вклад в мой код. Все 40 000+ запросов прошли гладко в одном пакете. Осталось добавить дополнительную проверку данных (чтобы быть в безопасности) и настроить процесс cron (на самом деле это не проблема).


person Eve    schedule 30.06.2020    source источник
comment
Я смотрю на один из файлов .sql, экспортированных из базы данных MySql под PhpAdmin. Первые 50 строк задают кодировку, часовой пояс и создают две таблицы. Все 122 записи для одной из таблиц вставляются с помощью одного оператора. Почти уверен, что я сделал тысячи одновременно в прошлом. Текст, выделенный желтым цветом по следующей ссылке, кажется недвусмысленным. sqlservertutorial.net/sql-server-basics/.   -  person enhzflep    schedule 01.07.2020
comment
@enhzflep Я не уверен, работает ли INSERT так же, как UPDATE в упомянутом вами контексте, потому что я также должен проверять предложение WHERE для каждого запроса.   -  person Eve    schedule 01.07.2020
comment
40000 запросов — это не так много, если только они не исходят от сотен одновременных клиентских запросов. Вы планируете позволить cron выполнить эту задачу, большинство современных серверов могут легко справиться с этим объемом.   -  person glinda93    schedule 01.07.2020
comment
Мне нужно, чтобы cron выполнял эту задачу несколько раз в день, поэтому я завершаю работу с конечной точкой API, а затем внутренне обновляю 40000 цен и количеств. Этот последний этап мне не нужно, чтобы занять слишком много времени. Приветствуется любой код для автоматизации процесса, как уже объяснялось, - в качестве ответа. Большое тебе спасибо.   -  person Eve    schedule 01.07.2020
comment
Комментарии @Eve не дают нам никакой репутации. SO предназначен для минимально воспроизводимых проблем. Я думаю, проблема в том, что вы задаете слишком много вопросов одновременно. Разделение его на более мелкие проблемы и размещение их в нескольких вопросах (или поиск их в SO) сделает людей менее несчастными.   -  person glinda93    schedule 02.07.2020
comment
@ Ева, я думаю, ты меня неправильно поняла. Разбиение большой проблемы на более мелкие, крошечные не для тех, кто отвечает ТАК. Это ради тебя. В процессе вы вспомните свою проблему и, возможно, получите представление о том, где вы ошиблись или что упустили. Кроме того, другим разработчикам будет намного проще лучше понять вашу проблему, и, надеюсь, многие из них помогут вам. Вы приняли мой ответ, но я сомневаюсь, что мой ответ действительно подходит для вашей проблемы. Поскольку ваш пост слишком длинный и задает много вопросов, я не был уверен, о чем вы спрашивали. Ни другие люди.   -  person glinda93    schedule 02.07.2020
comment
И если вы столкнулись с проблемой серийных отрицательных голосов (по фиксированному количеству сталкеров), обратитесь к модераторам SE. Но я думаю, что для вас в SO нет ничего лучше серийных отрицательных голосов.   -  person glinda93    schedule 02.07.2020
comment
@bravemaster, я играл с API в Postmaster, чтобы проверить ответы, предоставляемые конечной точкой. Они прошли хорошо, только один ответ имел более 25 МБ (поэтому мне пришлось загрузить большой двоичный объект на сервер, а затем продолжить его обработку). Почтальон загрузил большой двоичный объект в виде файла, но для меня это не вариант (потому что серверу приходилось ежедневно загружать его с помощью cron, а не вручную на моем компьютере). Он также предложил PHP-код curl, я использовал его в php-файле, в браузере другой сервер выдал мне ошибку CORS (из-за того, что мой php-файл не находится в том же домене). Я выбросил завиток и сделал обходной путь в ajax -›следовать   -  person Eve    schedule 02.07.2020
comment
@bravemaster -›бинго, сработало. Следующим шагом было обновление php в базе данных — потому что НЕ УВЕРЕН, КАК ЗАСТАВИТЬ ЭТО БЕЗОПАСНО РАБОТАТЬ с помощью «fetch .then .then» — подготовленные stmts по-прежнему остаются лучшими. Транзакции были выбраны из-за большого размера сохраненного большого двоичного объекта (и как способ упаковать данные в более мелкие фрагменты вместо слишком большого количества, подсчитать фрагменты и выполнить цикл до конца). Как вы можете видеть, было несколько аспектов, моя ошибка в том, что я не разделил все на большее количество вопросов, однако это был только мой первый день здесь и в целом не слишком положительный опыт для меня. Большое спасибо за финал.   -  person Eve    schedule 02.07.2020


Ответы (1)


Вам не нужно получать данные из внешнего API на клиентской стороне и отправлять их повторно на свой сервер.

PHP может отправлять запрос на получение и получать ответ без jQuery.

Вы можете написать php-скрипт для получения прайс-листа продуктов из внешнего API, например:

$url = "https://www.notimportant/etc/";
$header = ['GUID' => 'something']

echo "Sending request to API server...\n";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch,CURLOPT_HEADER, $header);
$response = curl_exec($ch);
echo "Received response\n";
curl_close($ch);

$data = json_decode($response);
echo "Product count: " . count($data) . "\n";
echo "Saving products to database...";
foreach($data as $productInfo) {
   save_data($productInfo); // you need to implement save_data
}
echo "Products saved. Exiting"\n;
exit(0);

Если вы хотите узнать, как записать строку (в данном случае ответ JSON) в файл, обратитесь к этому: Как поместить строку в текстовый файл в PHP?

Если вы хотите узнать, как выполнить запрос MySQL с использованием подготовленных операторов, обратитесь к этому: Как я могу предотвратить SQL-инъекцию в PHP?

Если вы хотите узнать, как отправить запрос https с настраиваемым заголовком, обратитесь к этому сообщению: Отправить запрос GET HTTPS с пользовательскими заголовками PHP

Если вы хотите узнать, как регулярно выполнять php-скрипт с помощью crontab, обратитесь к этому сообщению: Запуск файла PHP с помощью crontab

(Не волнуйтесь, это все записи stackoverflow)

Я бы использовал что-то элегантное, скажем, использовать 500 запросов в одной транзакции (и сделать все обновление всего за 80 шагов вместо полных 40 000 простых запросов), но не уверен, как все правильно настроить.

Транзакция не улучшит производительность, транзакция предназначена не для производительности, а для целостности данных. Сделать 40 000 простых запросов и выполнить их все сразу (или один за другим, если хотите) — лучший вариант.

person glinda93    schedule 01.07.2020