Пытаюсь в dle cms сохранять дополнительную информацию о пользователе в базу данных, при этом дополнительные данные получаю из JSON со стороннего сервиса.
Хочу запускать скрипт по крону и проверять соответствие email из json с email зарегистрированных на сайте пользователей dle_users - email
.
Образец json данных:
[{"id":323,"userId":676,"createdAt":"2019-08-23T13:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":812,"title":"Заголовок.","type":"DIGITAL","userId":728},"income":297.08,"customer":{"id":8832,"fio":"Петрович","email":"test@test.ru"}}, {"id":324,"userId":677,"createdAt":"2019-08-23T14:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":813,"title":"Заголовок.","type":"DIGITAL","userId":729},"income":297.08,"customer":{"id":8833,"fio":"Петрович","email":"test2@test.ru"}}, {"id":325,"userId":678,"createdAt":"2019-08-23T15:05:56.830Z","status":"APPROVED","currency":"RUR","paySystem":"visa","paySystemGroup":"creditCard","total":69.00,"ipAddress":"246.227.2.4","OneTimeOffer":false,"ware":{"id":814,"title":"Заголовок.","type":"DIGITAL","userId":730},"income":297.08,"customer":{"id":8834,"fio":"Петрович","email":"test3@test.ru"}}]
Json дерево
Всего в json ответе 10 позиций.
Мне нужно записать в базу данные id, ware->id, ware->title, income если найдено соответствие по email адресу.
Т.е. как-то нужно из json брать email и искать, не зарегистрирован ли на dle сайте пользователь с данным email адресом и если найден данный адрес, необходимо из json получить данные id, ware->id, ware->title, income и сохранить их в базу dle_users
, в таблицу данного пользователя.
На сайте может быть зарегистрировано несколько тысяч пользователей. Обращаться к Json хочу как можно чаще по cron, чтобы на сайте была всегда актуальная информация.
Как верно реализовать такой функционал, чтобы система не висла и при повторном обращении к json, ранее полученные данные вновь не добавлялись в базу (видимо нужно выполнить проверку по id из json, т.к. он уникален)?
Еще, очень важно, в образце базы я не добавил поля под id, ware->id, ware->title, income т.к. не знаю как сделать это верно, если проверять ранее размещенные данные, то id из json нужно сохранять так, чтобы он был уникальным...
Помогите разобраться, пожалуйста. Направьте в нужном направлении.
Спасибо.
Ниже дамп базы данных, таблица dle_users
--
-- Структура таблицы `dle_users`
--
CREATE TABLE `dle_users` (
`email` varchar(50) NOT NULL DEFAULT '',
`password` varchar(255) NOT NULL DEFAULT '',
`name` varchar(40) NOT NULL DEFAULT '',
`user_id` int(11) NOT NULL,
--
-- Добавил 4 поля
--
`paym_id` text,
`paym_ware_id` text,
`paym_title` text,
`paym_income` varchar(40) DEFAULT NULL,
`news_num` mediumint(8) NOT NULL DEFAULT '0',
`comm_num` mediumint(8) NOT NULL DEFAULT '0',
`user_group` smallint(5) NOT NULL DEFAULT '4',
`lastdate` varchar(20) NOT NULL DEFAULT '',
`reg_date` varchar(20) NOT NULL DEFAULT '',
`banned` varchar(5) NOT NULL DEFAULT '',
`allow_mail` tinyint(1) NOT NULL DEFAULT '1',
`info` text NOT NULL,
`signature` text NOT NULL,
`foto` varchar(255) NOT NULL DEFAULT '',
`fullname` varchar(100) NOT NULL DEFAULT '',
`land` varchar(100) NOT NULL DEFAULT '',
`favorites` text NOT NULL,
`pm_all` smallint(5) NOT NULL DEFAULT '0',
`pm_unread` smallint(5) NOT NULL DEFAULT '0',
`time_limit` varchar(20) NOT NULL DEFAULT '',
`xfields` text NOT NULL,
`allowed_ip` varchar(255) NOT NULL DEFAULT '',
`hash` varchar(32) NOT NULL DEFAULT '',
`logged_ip` varchar(46) NOT NULL DEFAULT '',
`restricted` tinyint(1) NOT NULL DEFAULT '0',
`restricted_days` smallint(4) NOT NULL DEFAULT '0',
`restricted_date` varchar(15) NOT NULL DEFAULT '',
`timezone` varchar(100) NOT NULL DEFAULT '',
`news_subscribe` tinyint(1) NOT NULL DEFAULT '0',
`comments_reply_subscribe` tinyint(1) NOT NULL DEFAULT '0',
`twofactor_auth` tinyint(1) NOT NULL DEFAULT '0',
`cat_add` varchar(500) NOT NULL DEFAULT '',
`cat_allow_addnews` varchar(500) NOT NULL DEFAULT ''
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
--
-- Дамп данных таблицы `dle_users`
--
INSERT INTO `dle_users` (`email`, `password`, `name`, `user_id`, `news_num`, `comm_num`, `user_group`, `lastdate`, `reg_date`, `banned`, `allow_mail`, `info`, `signature`, `foto`, `fullname`, `land`, `favorites`, `pm_all`, `pm_unread`, `time_limit`, `xfields`, `allowed_ip`, `hash`, `logged_ip`, `restricted`, `restricted_days`, `restricted_date`, `timezone`, `news_subscribe`, `comments_reply_subscribe`, `twofactor_auth`, `cat_add`, `cat_allow_addnews`) VALUES
('test2@test.ru', '$2y$10$HizMAqnS70XtPyOAhEOBZunFUr0sTf6BxuhAF.jF5064WmVSiInS2', 'Admin', 1, 5, 2, 1, '1566571668', '1566008190', '', 1, '', '', '', '', '', '', 0, 0, '', '', '', 'a5d5341fa03117f2046f5a0b8f1589d0', '33.212.78.120', 0, 0, '', '', 0, 0, 0, '', '');
--
-- Индексы сохранённых таблиц
--
--
-- Индексы таблицы `dle_users`
--
ALTER TABLE `dle_users`
ADD PRIMARY KEY (`user_id`),
ADD UNIQUE KEY `name` (`name`),
ADD UNIQUE KEY `email` (`email`);
--
-- AUTO_INCREMENT для сохранённых таблиц
--
--
-- AUTO_INCREMENT для таблицы `dle_users`
--
ALTER TABLE `dle_users`
MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
COMMIT;
Попробовал таким образом найти и сохранить данные, но что-то не получилось :(
Данные не сохраняются в базе.
$pdo = new PDO("mysql:host = localhost; dbname = ...",..., ...);
foreach ($parsedResponse as $singleUserResponse) {
$paymId = $singleUserResponse['id'];
$paymWareId = $singleUserResponse['ware']['id'];
$paymTitle = $singleUserResponse['ware']['title'];
$paymIncome = $singleUserResponse['income'];
$email = $singleUserResponse['customer']['email'];
$stmtGetUserByEmail = $pdo->prepare("SELECT email FROM dle_users WHERE email='$email' AND paym_income IS NULL");
//var_dump ($stmtGetUserByEmail);
// SELECT email FROM dle_users WHERE email='test@test.ru' AND paym_income IS NULL
//перечисляются email не из базы, а из json
$stmtGetUserByEmail->execute([$email]);
$userId = $stmtGetUserByEmail->fetch();
if ($userId) {
$updateUserPaymData = $pdo->prepare("UPDATE dle_users SET paym_income = $paymIncome, paym_id = $paymId WHERE email= $email");
$stmt= $pdo->prepare($updateUserPaymData);
$stmt->execute([$paymIncome, $paymId, $userId]);
}
}
Один из способов решить такую задачу -- сделать просто, потом оптимзировать. Попробуем?
Шаг 1.
Т.е. как-то нужно из json брать email и искать, не зарегистрирован ли на dle сайте пользователь с данным email адресом и если найден данный адрес, необходимо из json получить данные id, ware->id, ware->title, income и сохранить их в базу dle_users, в таблицу данного пользователя.
Распарсили json и бежим по коллекции
$payments = json_decode($json, true);
foreach ($payments as $payment) {
$db->query('
update `dle_users`
set
`paym_id` = :paym_id,
`paym_ware_id` = :paym_ware_id,
`paym_title` = :paym_title,
`paym_income` = :paym_income
where `email` = :email
', [
':paym_id' => $payment['id'],
':paym_ware_id' => $payment['ware']['id'],
':paym_title' => $payment['ware']['title'],
':paym_income' => $payment['income'],
':email' => $payment['customer']['email'],
]);
}
$db->query
придумал чтобы сократить немного код и донести смысл. Если делать такой запрос в базу, то всё работает!
Шаг 2.
Вы наверняка слышали про запросы в базу в цикле :-) Такое решение не самое производительное. Время оптимизаций!
Мы можем бахнуть все обновления одним запросом. UPDATE
так не умеет, но умеет INSERT ON DUPLICATE KEY UPDATE
.
$db->query('
insert into `dle_users` (
`id`,
`paym_id`,
`paym_ware_id`,
`paym_title`,
`paym_income`
) values :values
on duplicate key update
`paym_id` = values(`paym_id`),
`paym_ware_id` = values(`paym_ware_id`),
`paym_title` = values(`paym_title`),
`paym_income` = values(`paym_income`)
', [':values' => $values]);
Мы могли бы сделать только один запрос на обновление, если бы у нас были id пользователй с имэйлами из json. Слава богу добыть id совсем не сложно!
$payments = json_decode($json, true);
$emails = array_map(function($payment) {
return $payment['customer']['email'];
}, $payments);
$result = $db->query('
select
`id`,
`email`
from `dle_users`
where `email` in (:emails)
', [':emails' => $emails]);
$dleUsersIndex = array_column($result->rows(), null, 'email');
$values = [];
foreach ($payments as $payment) {
$dleUser = $dleUsersIndex[$payment['customer']['email']]?? null;
if ($dleUser) {
$values[] = [
$dleUser['id'],
$payment['id'],
$payment['ware']['id'],
$payment['ware']['title'],
$payment['income'],
];
}
}
Делаем запрос INSERT ON DUPLICATE KEY UPDATE
и все обновления прилетают в одном запросе! Если это бы это был InnoDB, то нам были не страшны одновременные вызовы (один запрос либо выполнится либо нет, данные будут согласованы). В MyISAM можно лочить таблицу или забить на всю эту консистентность :-)
Тут можно остановится.
Шаг 3 (для особо упорных).
Сейчас всё это добро притормаживает на поиске пользователей в базе по имэйлу. Это происходит из-за того, что по полю email
нет индекса. Надо построить. Но загвоздка том, что b-tree индекс по varchar не самое клёвое решение (его немного раздует на миллионах записей). Выручил бы хэш-индекс, но в MyISAM и InnoDB их нет :-(
Но не стоит унывать! Давно прдуман хак с эрзацем хэш-индекса. Просто добовляем в таблицу вычисляемое поле emailHash binary(16) not null
и при изменении имэйла записываем туда сырой (не шестнадцатеричное представление) md5 (вообще можно использовать и другие хэш-функции). Ну и строим индекс по нему, а потом при поиске по имэйлу добавляем в условие emailHash = unhex(md5(:email))
. Теперь вообще будет летать.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
У меня такая проблема: есть бд, в которой есть таблица users, и мне нужно эту самую таблицу вывести, но выводится только слово 'Array' и больше ничегоВот...
Проблемма вот такаяЕсть у меня интернет магазин на Laravel 5
Подскажите пожалуйста как создать функцию Gol на php по обработке строки вида "36,67,90,90"Это минуты забитых голов в футбольном матче