Я создаю веб-приложение, используя Laravel 5, который создает ссылки на веб-приложение, которое при посещении показывает форму для отчета о прохождении студента. Эти ссылки отправляются веб-приложением по электронной почте контакта в учреждении, на котором студент посещает, чтобы получатель мог завершить отчет о ходе, доступ к которому был получен по ссылке в письме.
Проблема, с которой я сталкиваюсь, заключается в создании и отправке ссылок. У меня есть код, который отлично работает с несколькими сотнями студентов, однако в реальном мире приложение будет потенциально создавать и отправлять 3000+ или около того ссылок за один раз. Код, который я написал, просто не может обрабатывать такое большое количество своевременно, и приложение сработает. Как ни странно, хотя я не получаю никакой ошибки таймаута через laravel (мне нужно будет дважды проверить журналы php).
Хотя я более чем приветствую другие предложения, я считаю, что ответ на проблему заключается в использовании очередей. Я уже использовал очереди при отправке электронной почты (см. Код), но я хотел бы работать с некоторыми другими частями кода в очереди, но я немного не уверен, как это сделать!
Краткая схема базы данных
Student
hasMany Link
Student
hasMany InstitutionContact
(ограничено двумя моими приложениями)
Link
hasMany InstitutionContact
(ограничено двумя моими приложениями)
Email
manyToMany Link
Что я пытаюсь выполнить
-
Получите все
Student
, которым требуется новыйLink
-
Создайте
Link
для каждогоStudent
-
Назначьте
Student
текущийInstitutionContact
контактуLink
InstitutionContact
(AStudent
, который может измениться, поэтому я свяжу ссылкуInstitutionContact
с ссылкой, если это необходимо для повторной отправки. -
Прокрутите все вновь созданное
Links
, чтобы сгруппировать их совместно с общимInstitutionContact
- это значит, что электронное письмо не отправляется на ссылку (таким образом, возможно, отправка нескольких писем с одной ссылкой на тот же адрес), скорее, ссылки должны быть сгруппированы одним и тем же адресом электронной почты и отправлены вместе, если это применимо -
Прокрутите все
Link
, сгруппированные по электронной почте/контакту и:- Отправьте электронное письмо с адресом
Link
(url, имя студента и т.д.) на указанный адрес электронной почтыInstitutionContact
- Напишите копию
Email
в базу данных - Присоединиться к
Email
, созданному в предыдущем шаге, кLink
(s), которые были отправлены в нем (чтобы приложение можно было использовать для поиска, какая ссылка была отправлена в этом письме)
- Отправьте электронное письмо с адресом
Таким образом, главная задача, с которой я сталкиваюсь, заключается в выполнении вышеупомянутой задачи с большим набором данных. Я уже рассмотрел создание и отправку Link
один за другим через очередь, однако это не позволило бы мне группировать все Link
вместе путем контакта/электронной почты. Поскольку задача не будет выполняться регулярно, я был бы открыт для рассмотрения выполнения задачи, поскольку это связано с увеличением объема памяти и времени для процесса, однако я не имел большого успеха при попытке использовать это с помощью set_time_limit(0);
и ini_set('memory_limit','1056M');
перед отправкой любых ссылок.
Любая помощь будет действительно оценена, спасибо, если вы прочтете это!
код
приложение\Http\Контроллеры\LinkController.php
public function storeAndSendMass(Request $request)
{
$this->validate($request, [
'student_id' => 'required|array',
'subject' => 'required|max:255',
'body' => 'required|max:5000',
]);
$studentIds = $request->get('student_id');
$subject = $request->get('subject');
$body = $request->get('body');
$students = $this->student
->with('institutionContacts')
->whereIn('id', $studentIds)
->where('is_active', 1)
->get();
// create link, see Link.php below for method
$newLinks = $this->link->createActiveLink($students);
// send link to student contact(s), see LinkEmailer.php below for method
$this->linkEmailer->send($newLinks, ['subject' => $subject, 'body' => $body], 'mass');
// return
return response()->json([
'message' => 'Creating and sending links'
]);
}
приложение \Models\Link.php
public function createActiveLink($students)
{
$links = [];
foreach ($students as $student) {
$newLink = $this->create([
'token' => $student->id, // automatically hashed
'status' => 'active',
'sacb_refno' => $student->sacb_refno,
'course_title' => $student->course_title,
'university_id' => $student->university_id,
'student_id' => $student->id,
'institution_id' => $student->institution_id,
'course_id' => $student->course_id,
]);
$studentContacts = $student->institutionContacts;
if ($studentContacts) {
foreach ($studentContacts as $studentContact) {
$newLink->contacts()->create([
'type' => $studentContact->pivot->type,
'institution_contact_id' => $studentContact->pivot->institution_contact_id
]);
$newLink->save();
}
}
$links[] = $newLink->load('student');
}
return $links;
}
Приложение\Письма\LinkEmailer.php
namespace App\Emails;
use App\Emails\EmailComposer;
class LinkEmailer
{
protected $emailComposer;
public function __construct(EmailComposer $emailComposer)
{
$this->emailComposer = $emailComposer;
}
public function send($links, $emailDetails, $emailType)
{
$contactsAndLinks = $this->arrangeContactsToLinks($links);
foreach ($contactsAndLinks as $linksAndContact) {
$emailData = array_merge($linksAndContact, $emailDetails);
// send/queue email
\Mail::queue('emails/queued/reports', $emailData, function ($message) use ($emailData) {
$message
->to($emailData['email'], $emailData['formal_name'])
->subject($emailData['subject']);
});
// compose email message, returns text of the email
$emailMessage = $this->emailComposer->composeMessage($emailData);
// // create Email
$email = \App\Models\Email::create([
'to' => $emailData['email'],
'from' => '[email protected]',
'subject' => $emailData['subject'],
'body' => $emailMessage,
'type' => $emailType,
'user' => $_SERVER['REMOTE_USER']
]);
foreach ($linksAndContact['links'] as $link) {
$link->emails()->attach($email->id);
}
}
}
// group links by contact
public function arrangeContactsToLinks($links)
{
$contactsForLinks = [];
$assigned = false;
$match = false;
foreach ($links as $link) { // 1, n
if ($link->contacts) {
foreach ($link->contacts as $contact) { // 1, 2
if ($contactsForLinks) {
$assigned = false;
foreach ($contactsForLinks as $key => $contactLink) { // n
// assign links to existing email in array
if ($contactLink['email'] === $contact->institutionContact->email) {
$match = false;
// check link hasn't already been included
foreach ($contactsForLinks[$key]['links'] as $assignedLink) {
if ($assignedLink === $link) {
$match = true;
}
}
// if there was no match add to list of links
if (!$match) {
$contactsForLinks[$key]['links'][] = $link->load('student');
$assigned = true;
break;
}
}
}
if (!$assigned) {
$contactsForLinks[] = [
'email' => $contact->institutionContact->email,
'formal_name' => $contact->institutionContact->formal_name,
'requires_id' => $contact->institutionContact->institution->requires_id,
'requires_course_title' => $contact->institutionContact->institution->requires_course_title,
'links' => [$link->load('student')],
];
}
} else {
$contactsForLinks[] = [
'email' => $contact->institutionContact->email,
'formal_name' => $contact->institutionContact->formal_name,
'requires_id' => $contact->institutionContact->institution->requires_id,
'requires_course_title' => $contact->institutionContact->institution->requires_course_title,
'links' => [$link->load('student')],
];
}
}
}
}
return $contactsForLinks;
}
}
Изменить 1
У меня теперь это работает с set_time_limit(0);
и ini_set('memory_limit','1056M');
, для выполнения 3000 учеников потребовалось 8 минут.
Изменить 2
Я запускаю Laravel Framework версии 5.1.6 (LTS), MySQL для DB.
Изменить 3
Оцените все ответы до сих пор, спасибо всем. Я думаю, что я могу запустить процесс создания Link
в очередь, которая будет иметь связанный объект в базе данных, называемый как-то вроде Batch
, и когда этот Batch
ссылок будет создан, затем сгруппируйте все Link
от этого Batch
и отправить их.
Я мог бы использовать подход, который предложил @denis-mysenko, имея поле sent_at
в таблице Link
и планируя процесс проверки Link
, который не был отправлен, а затем отправил их. Однако, используя вышеупомянутый подход, я могу отправить Batch
из Link
, когда все они будут созданы, тогда как с подходом sent_at
с запланированным процессом, который ищет Link
, который не был отправлен, он может потенциально отправить некоторые ссылки, когда все ссылки еще не созданы.