自动推广
This commit is contained in:
@@ -1082,6 +1082,7 @@ class EmailClient extends Base
|
||||
* - min_interval, max_interval (seconds between emails)
|
||||
* - max_bounce_rate (%), no_repeat_days
|
||||
* - send_start_hour, send_end_hour (UTC, default 8-22)
|
||||
* - send_date (Y-m-d,计划发送日期;有则「今日准备明日发」由定时任务处理,无则创建后需手动 startTask)
|
||||
*/
|
||||
public function createTask()
|
||||
{
|
||||
@@ -1097,16 +1098,17 @@ class EmailClient extends Base
|
||||
$minInterval = intval($this->request->param('min_interval', 30));
|
||||
$maxInterval = intval($this->request->param('max_interval', 60));
|
||||
$maxBounceRate = intval($this->request->param('max_bounce_rate', 5));
|
||||
$noRepeatDays = intval($this->request->param('no_repeat_days', 30));
|
||||
$noRepeatDays = intval($this->request->param('no_repeat_days', 7));
|
||||
$sendStartHour = intval($this->request->param('send_start_hour', 8));
|
||||
$sendEndHour = intval($this->request->param('send_end_hour', 22));
|
||||
$sendDate = trim($this->request->param('send_date', date("Y-m-d",strtotime('+1 day'))));
|
||||
|
||||
if (!$journalId || !$templateId) {
|
||||
return jsonError('journal_id and template_id are required');
|
||||
}
|
||||
if (empty($expertIds) && empty($field)) {
|
||||
return jsonError('expert_ids or field is required');
|
||||
}
|
||||
// if (empty($expertIds) && empty($field)) {
|
||||
// return jsonError('expert_ids or field is required');
|
||||
// }
|
||||
|
||||
$tpl = Db::name('mail_template')
|
||||
->where('template_id', $templateId)
|
||||
@@ -1123,36 +1125,88 @@ class EmailClient extends Base
|
||||
|
||||
$experts = [];
|
||||
if (!empty($expertIds)) {
|
||||
// 显式点名的专家,只按 state 过滤,ltime 由外部自行控制
|
||||
$ids = array_map('intval', explode(',', $expertIds));
|
||||
$experts = Db::name('expert')->where('expert_id', 'in', $ids)->where('state', 0)->select();
|
||||
$experts = Db::name('expert')
|
||||
->where('expert_id', 'in', $ids)
|
||||
->where('state', 0)
|
||||
->select();
|
||||
} else {
|
||||
// 一般情况:不传 expert_ids,根据期刊实际领域自动选择专家
|
||||
$journal = Db::name('journal')->where('journal_id', $journalId)->find();
|
||||
if (!$journal || empty($journal['issn'])) {
|
||||
return jsonError('Journal or ISSN not found');
|
||||
}
|
||||
|
||||
// 期刊绑定的领域(major_title),与 dailyFetchAll 保持一致
|
||||
$majors = Db::name('major_to_journal')
|
||||
->alias('mtj')
|
||||
->join('t_major m', 'm.major_id = mtj.major_id', 'left')
|
||||
->where('mtj.journal_issn', $journal['issn'])
|
||||
->where('mtj.mtj_state', 0)
|
||||
->where('m.major_state', 0)
|
||||
->column('m.major_title');
|
||||
|
||||
$majors = array_unique(array_filter($majors));
|
||||
|
||||
$query = Db::name('expert')->alias('e')
|
||||
->join('t_expert_field ef', 'e.expert_id = ef.expert_id')
|
||||
->where('e.state', 0)
|
||||
->where('ef.state', 0);
|
||||
|
||||
if ($field) {
|
||||
$query->where('ef.field', 'like', '%' . $field . '%');
|
||||
}
|
||||
if ($majorId) {
|
||||
$query->where('ef.major_id', $majorId);
|
||||
}
|
||||
|
||||
$experts = $query->field('e.*')->group('e.expert_id')->select();
|
||||
}
|
||||
|
||||
if ($noRepeatDays > 0) {
|
||||
$cutoff = time() - ($noRepeatDays * 86400);
|
||||
$experts = array_filter($experts, function ($e) use ($cutoff) {
|
||||
return intval($e['ltime']) < $cutoff;
|
||||
// 领域条件:
|
||||
// 1) 若前端显式传了 field/major_id,则优先按传入条件过滤
|
||||
// 2) 否则,按期刊绑定领域(major_title)在 ef.field 中模糊匹配
|
||||
$query->where(function ($q) use ($field, $majorId, $majors) {
|
||||
if ($field !== '') {
|
||||
$q->where('ef.field', 'like', '%' . $field . '%');
|
||||
} elseif ($majorId > 0) {
|
||||
$q->where('ef.major_id', $majorId);
|
||||
} elseif (!empty($majors)) {
|
||||
$q->where(function ($qq) use ($majors) {
|
||||
foreach ($majors as $idx => $title) {
|
||||
$title = trim($title);
|
||||
if ($title === '') {
|
||||
continue;
|
||||
}
|
||||
if ($idx === 0) {
|
||||
$qq->where('ef.field', 'like', '%' . $title . '%');
|
||||
} else {
|
||||
$qq->whereOr('ef.field', 'like', '%' . $title . '%');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
$experts = array_values($experts);
|
||||
|
||||
// 不频繁发送:在 SQL 中直接使用 ltime + no_repeat_days 过滤
|
||||
if ($noRepeatDays > 0) {
|
||||
$cutoff = time() - ($noRepeatDays * 86400);
|
||||
$query->where(function ($q) use ($cutoff) {
|
||||
$q->where('e.ltime', 0)->whereOr('e.ltime', '<', $cutoff);
|
||||
});
|
||||
}
|
||||
|
||||
// 去重同一个专家
|
||||
$experts = $query
|
||||
->field('e.*')
|
||||
->group('e.expert_id')
|
||||
->limit(100)
|
||||
->select();
|
||||
}
|
||||
|
||||
if (empty($experts)) {
|
||||
return jsonError('No eligible experts found (all may have been promoted recently)');
|
||||
}
|
||||
|
||||
$sendDateVal = null;
|
||||
if ($sendDate !== '') {
|
||||
$ts = strtotime($sendDate);
|
||||
if ($ts !== false) {
|
||||
$sendDateVal = date('Y-m-d', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$taskId = Db::name('promotion_task')->insertGetId([
|
||||
'journal_id' => $journalId,
|
||||
@@ -1172,6 +1226,7 @@ class EmailClient extends Base
|
||||
'no_repeat_days' => $noRepeatDays,
|
||||
'send_start_hour' => $sendStartHour,
|
||||
'send_end_hour' => $sendEndHour,
|
||||
'send_date' => $sendDateVal,
|
||||
'ctime' => $now,
|
||||
'utime' => $now,
|
||||
]);
|
||||
@@ -1192,14 +1247,85 @@ class EmailClient extends Base
|
||||
}
|
||||
Db::name('promotion_email_log')->insertAll($logs);
|
||||
|
||||
$msg = 'Task created, call startTask to begin sending';
|
||||
if ($sendDateVal) {
|
||||
$msg = 'Task created for send_date=' . $sendDateVal . ', will be prepared by cron and triggered on that day';
|
||||
}
|
||||
return jsonSuccess([
|
||||
'task_id' => $taskId,
|
||||
'total_count' => count($experts),
|
||||
'state' => 0,
|
||||
'msg' => 'Task created, call startTask to begin sending',
|
||||
'send_date' => $sendDateVal,
|
||||
'msg' => $msg,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个任务预生成邮件(可手动或测试用)
|
||||
* Params: task_id
|
||||
*/
|
||||
public function prepareTask()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_id', 0));
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$service = new PromotionService();
|
||||
$result = $service->prepareTask($taskId);
|
||||
|
||||
if ($result['error']) {
|
||||
return jsonError($result['error']);
|
||||
}
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务:为指定日期的任务预生成邮件(默认明天)
|
||||
* 建议每天 22:00 执行:curl .../EmailClient/prepareTasksForDate 或 prepareTasksForDate?date=2026-03-12
|
||||
*/
|
||||
public function prepareTasksForDate()
|
||||
{
|
||||
$date = trim($this->request->param('date', ''));
|
||||
if ($date === '') {
|
||||
$date = date('Y-m-d', strtotime('+1 day'));
|
||||
} else {
|
||||
$ts = strtotime($date);
|
||||
if ($ts === false) {
|
||||
return jsonError('date invalid, use Y-m-d');
|
||||
}
|
||||
$date = date('Y-m-d', $ts);
|
||||
}
|
||||
|
||||
$service = new PromotionService();
|
||||
$result = $service->prepareTasksForDate($date);
|
||||
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务:触发指定日期的已准备任务开始发送(默认今天)
|
||||
* 建议每天 8:00 执行:curl .../EmailClient/triggerTasksForDate 或 triggerTasksForDate?date=2026-03-12
|
||||
*/
|
||||
public function triggerTasksForDate()
|
||||
{
|
||||
$date = trim($this->request->param('date', ''));
|
||||
if ($date === '') {
|
||||
$date = date('Y-m-d');
|
||||
} else {
|
||||
$ts = strtotime($date);
|
||||
if ($ts === false) {
|
||||
return jsonError('date invalid, use Y-m-d');
|
||||
}
|
||||
$date = date('Y-m-d', $ts);
|
||||
}
|
||||
|
||||
$service = new PromotionService();
|
||||
$result = $service->startTasksForDate($date);
|
||||
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or resume a promotion task
|
||||
*/
|
||||
@@ -1223,6 +1349,10 @@ class EmailClient extends Base
|
||||
if ($task['state'] == 1) {
|
||||
return jsonError('Task is already running');
|
||||
}
|
||||
// state=0 草稿 或 state=5 已准备 均可启动
|
||||
if ($task['state'] != 0 && $task['state'] != 5) {
|
||||
return jsonError('Task can only be started when state is draft(0) or prepared(5), current: ' . $task['state']);
|
||||
}
|
||||
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update([
|
||||
'state' => 1,
|
||||
|
||||
Reference in New Issue
Block a user