1
This commit is contained in:
@@ -29,21 +29,28 @@ class Country extends Base
|
|||||||
$page = max(1, intval($this->request->param('page', 1)));
|
$page = max(1, intval($this->request->param('page', 1)));
|
||||||
$perPage = max(1, min(intval($this->request->param('per_page', 20)), 100));
|
$perPage = max(1, min(intval($this->request->param('per_page', 20)), 100));
|
||||||
|
|
||||||
$query = Db::name('country');
|
$where = [];
|
||||||
|
|
||||||
if ($keyword !== '') {
|
if ($keyword !== '') {
|
||||||
$query->where('zh_name|en_name|code', 'like', '%' . $keyword . '%');
|
$where[] = ['zh_name|en_name|code', 'like', '%' . $keyword . '%'];
|
||||||
}
|
}
|
||||||
if ($isHot !== '' && $isHot !== '-1') {
|
if ($isHot !== '' && $isHot !== '-1') {
|
||||||
$query->where('is_hot', intval($isHot));
|
$where[] = ['is_hot', '=', intval($isHot)];
|
||||||
}
|
}
|
||||||
if ($partition !== '' && $partition !== '-1') {
|
if ($partition !== '' && $partition !== '-1') {
|
||||||
$query->where('partition', intval($partition));
|
$where[] = ['partition', '=', intval($partition)];
|
||||||
}
|
}
|
||||||
|
|
||||||
$total = (clone $query)->count();
|
$countQuery = Db::name('country');
|
||||||
|
foreach ($where as $w) {
|
||||||
|
$countQuery->where($w[0], $w[1], $w[2]);
|
||||||
|
}
|
||||||
|
$total = $countQuery->count();
|
||||||
|
|
||||||
$list = $query
|
$listQuery = Db::name('country');
|
||||||
|
foreach ($where as $w) {
|
||||||
|
$listQuery->where($w[0], $w[1], $w[2]);
|
||||||
|
}
|
||||||
|
$list = $listQuery
|
||||||
->order('is_hot desc, zh_name asc')
|
->order('is_hot desc, zh_name asc')
|
||||||
->page($page, $perPage)
|
->page($page, $perPage)
|
||||||
->select();
|
->select();
|
||||||
|
|||||||
@@ -1381,7 +1381,7 @@ class EmailClient extends Base
|
|||||||
$taskName = trim($this->request->param('task_name', ''));
|
$taskName = trim($this->request->param('task_name', ''));
|
||||||
$expertIds = trim($this->request->param('expert_ids', ''));
|
$expertIds = trim($this->request->param('expert_ids', ''));
|
||||||
$field = trim($this->request->param('field', ''));
|
$field = trim($this->request->param('field', ''));
|
||||||
$majorId = intval($this->request->param('major_id', 0));
|
$fetchIds = trim($this->request->param('fetch_ids', ''));
|
||||||
$smtpIds = trim($this->request->param('smtp_ids', ''));
|
$smtpIds = trim($this->request->param('smtp_ids', ''));
|
||||||
$minInterval = intval($this->request->param('min_interval', 30));
|
$minInterval = intval($this->request->param('min_interval', 30));
|
||||||
$maxInterval = intval($this->request->param('max_interval', 60));
|
$maxInterval = intval($this->request->param('max_interval', 60));
|
||||||
@@ -1390,16 +1390,17 @@ class EmailClient extends Base
|
|||||||
$sendStartHour = intval($this->request->param('send_start_hour', 8));
|
$sendStartHour = intval($this->request->param('send_start_hour', 8));
|
||||||
$sendEndHour = intval($this->request->param('send_end_hour', 22));
|
$sendEndHour = intval($this->request->param('send_end_hour', 22));
|
||||||
$sendDate = trim($this->request->param('send_date', date("Y-m-d",strtotime('+1 day'))));
|
$sendDate = trim($this->request->param('send_date', date("Y-m-d",strtotime('+1 day'))));
|
||||||
|
$targetPartitions = trim($this->request->param('target_partitions', ''));
|
||||||
|
$targetCountryIds = trim($this->request->param('target_country_ids', ''));
|
||||||
|
$type = intval($this->request->param('type', 0));
|
||||||
|
$expertType = intval($this->request->param('expert_type', 5));
|
||||||
|
|
||||||
if (!$journalId || !$templateId) {
|
if (!$journalId) {
|
||||||
return jsonError('journal_id and template_id are required');
|
return jsonError('journal_id is required');
|
||||||
}
|
}
|
||||||
$journal_info = $this->journal_obj->where("journal_id",$journalId)->find();
|
$journal_info = $this->journal_obj->where("journal_id",$journalId)->find();
|
||||||
// if (empty($expertIds) && empty($field)) {
|
|
||||||
// return jsonError('expert_ids or field is required');
|
|
||||||
// }
|
|
||||||
$templateId = ($templateId == 0) ? intval($journal_info['default_template_id']) : $templateId;
|
$templateId = ($templateId == 0) ? intval($journal_info['default_template_id']) : $templateId;
|
||||||
$styleId = ($styleId == 0) ? intval($journal_info['default_style_id']) : $styleId;
|
$styleId = ($styleId == 0) ? intval(isset($journal_info['default_style_id']) ? $journal_info['default_style_id'] : 0) : $styleId;
|
||||||
if($templateId==0){
|
if($templateId==0){
|
||||||
return jsonError("template is not set!");
|
return jsonError("template is not set!");
|
||||||
}
|
}
|
||||||
@@ -1418,60 +1419,23 @@ class EmailClient extends Base
|
|||||||
|
|
||||||
$experts = [];
|
$experts = [];
|
||||||
if (!empty($expertIds)) {
|
if (!empty($expertIds)) {
|
||||||
// 显式点名的专家,只按 state 过滤,ltime 由外部自行控制
|
|
||||||
$ids = array_map('intval', explode(',', $expertIds));
|
$ids = array_map('intval', explode(',', $expertIds));
|
||||||
$experts = Db::name('expert')
|
$experts = Db::name('expert')
|
||||||
->where('expert_id', 'in', $ids)
|
->where('expert_id', 'in', $ids)
|
||||||
->where('state', 0)
|
->where('state', 0)
|
||||||
->select();
|
->select();
|
||||||
} else {
|
} else {
|
||||||
// 不传 expert_ids:根据期刊选定的推广领域(journal_promotion_field → expert_fetch)自动选择专家
|
// 领域来源优先级:前端传 field > 前端传 fetch_ids > journal_promotion_field
|
||||||
$journal = Db::name('journal')->where('journal_id', $journalId)->find();
|
$fields = [];
|
||||||
if (!$journal) {
|
|
||||||
return jsonError('Journal not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$promotionFields = Db::name('journal_promotion_field')
|
|
||||||
->alias('jpf')
|
|
||||||
->join('t_expert_fetch ef_fetch', 'ef_fetch.expert_fetch_id = jpf.expert_fetch_id', 'inner')
|
|
||||||
->where('jpf.journal_id', $journalId)
|
|
||||||
->where('jpf.state', 0)
|
|
||||||
->where('ef_fetch.state', 0)
|
|
||||||
->column('ef_fetch.field');
|
|
||||||
$promotionFields = array_unique(array_filter(array_map('trim', $promotionFields)));
|
|
||||||
|
|
||||||
$query = Db::name('expert')->alias('e')
|
|
||||||
->join('t_expert_field ef', 'e.expert_id = ef.expert_id', 'inner')
|
|
||||||
->where('e.state', 0)
|
|
||||||
->where('ef.state', 0);
|
|
||||||
|
|
||||||
// 前端显式传了 field 则优先,否则走期刊选定的推广领域
|
|
||||||
if ($field !== '') {
|
if ($field !== '') {
|
||||||
$query->where('ef.field', 'like', '%' . $field . '%');
|
$fields = [$field];
|
||||||
} elseif (!empty($promotionFields)) {
|
} elseif ($fetchIds !== '') {
|
||||||
$query->where(function ($q) use ($promotionFields) {
|
$fields = $this->resolveFieldsByFetchIds($fetchIds);
|
||||||
foreach ($promotionFields as $idx => $fieldName) {
|
|
||||||
if ($idx === 0) {
|
|
||||||
$q->where('ef.field', 'like', '%' . $fieldName . '%');
|
|
||||||
} else {
|
} else {
|
||||||
$q->whereOr('ef.field', 'like', '%' . $fieldName . '%');
|
$fields = $this->resolveFieldsByJournal($journalId);
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($noRepeatDays > 0) {
|
$experts = $this->findEligibleExperts($fields, $noRepeatDays, 100, $targetPartitions, $targetCountryIds);
|
||||||
$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)) {
|
if (empty($experts)) {
|
||||||
@@ -1489,9 +1453,12 @@ class EmailClient extends Base
|
|||||||
$now = time();
|
$now = time();
|
||||||
$taskId = Db::name('promotion_task')->insertGetId([
|
$taskId = Db::name('promotion_task')->insertGetId([
|
||||||
'journal_id' => $journalId,
|
'journal_id' => $journalId,
|
||||||
|
'factory_id' => 0,
|
||||||
'template_id' => $templateId,
|
'template_id' => $templateId,
|
||||||
'style_id' => $styleId,
|
'style_id' => $styleId,
|
||||||
'scene' => $scene,
|
'scene' => $scene,
|
||||||
|
'type' => $type,
|
||||||
|
'expert_type' => $expertType,
|
||||||
'task_name' => $taskName ?: ('Task ' . date('Y-m-d H:i')),
|
'task_name' => $taskName ?: ('Task ' . date('Y-m-d H:i')),
|
||||||
'total_count' => count($experts),
|
'total_count' => count($experts),
|
||||||
'sent_count' => 0,
|
'sent_count' => 0,
|
||||||
@@ -1506,6 +1473,8 @@ class EmailClient extends Base
|
|||||||
'send_start_hour' => $sendStartHour,
|
'send_start_hour' => $sendStartHour,
|
||||||
'send_end_hour' => $sendEndHour,
|
'send_end_hour' => $sendEndHour,
|
||||||
'send_date' => $sendDateVal,
|
'send_date' => $sendDateVal,
|
||||||
|
'target_partitions' => $targetPartitions,
|
||||||
|
'target_country_ids' => $targetCountryIds,
|
||||||
'ctime' => $now,
|
'ctime' => $now,
|
||||||
'utime' => $now,
|
'utime' => $now,
|
||||||
]);
|
]);
|
||||||
@@ -1931,6 +1900,33 @@ class EmailClient extends Base
|
|||||||
$item['progress'] = $item['total_count'] > 0
|
$item['progress'] = $item['total_count'] > 0
|
||||||
? round(($item['sent_count'] + $item['fail_count']) / $item['total_count'] * 100, 1)
|
? round(($item['sent_count'] + $item['fail_count']) / $item['total_count'] * 100, 1)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
$tp = isset($item['target_partitions']) ? trim((string)$item['target_partitions']) : '';
|
||||||
|
$tc = isset($item['target_country_ids']) ? trim((string)$item['target_country_ids']) : '';
|
||||||
|
if ($tp === '' && $tc === '') {
|
||||||
|
$item['country_scope_label'] = '全部国家';
|
||||||
|
} else {
|
||||||
|
$parts = [];
|
||||||
|
if ($tp !== '') {
|
||||||
|
$parts[] = '分区' . $tp;
|
||||||
|
}
|
||||||
|
if ($tc !== '') {
|
||||||
|
$cids = array_map('intval', explode(',', $tc));
|
||||||
|
$names = Db::name('country')->where('country_id', 'in', $cids)->column('zh_name');
|
||||||
|
if (!empty($names)) {
|
||||||
|
$parts[] = implode(',', $names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$item['country_scope_label'] = implode(' + ', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fid = isset($item['factory_id']) ? intval($item['factory_id']) : 0;
|
||||||
|
if ($fid > 0) {
|
||||||
|
$fRow = Db::name('promotion_factory')->where('promotion_factory_id', $fid)->field('promotion_factory_id, type, fetch_ids')->find();
|
||||||
|
$item['factory_name'] = $fRow ? ('工厂#' . $fid) : '已删除';
|
||||||
|
} else {
|
||||||
|
$item['factory_name'] = '手动创建';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonSuccess([
|
return jsonSuccess([
|
||||||
@@ -2124,35 +2120,35 @@ class EmailClient extends Base
|
|||||||
* 每日自动生成推广任务(由 Linux crontab 调用)
|
* 每日自动生成推广任务(由 Linux crontab 调用)
|
||||||
*
|
*
|
||||||
* 逻辑:
|
* 逻辑:
|
||||||
* 1. 查询所有 start_promotion=1 且 state=0 的期刊
|
* 1. 查询所有 state=0 的任务工厂(当前仅处理 expert_type=5)
|
||||||
* 2. 对每个期刊,检查明天是否已存在任务,避免重复
|
* 2. JOIN journal 确认期刊有效(state=0, start_promotion=1)
|
||||||
* 3. 用 default_template_id / default_style_id 创建 promotion_task
|
* 3. 按 factory_id + send_date 检查去重
|
||||||
* 4. 根据期刊绑定的领域自动筛选专家(排除近7天已联系、排除非正常状态)
|
* 4. template/style: 工厂 > 0 用工厂的,否则用期刊默认
|
||||||
* 5. 每个期刊默认生成 100 封邮件
|
* 5. 用工厂的 fetch_ids 查领域,用工厂的 target_partitions/target_country_ids 做国家过滤
|
||||||
|
* 6. 生成 promotion_task + promotion_email_log
|
||||||
*
|
*
|
||||||
* crontab 示例(每天凌晨1点执行):
|
* crontab 示例(每天凌晨1点执行):
|
||||||
* 0 1 * * * curl -s "https://your-domain.com/api/email_client/cronDailyCreateTasks" >> /var/log/promotion_cron.log 2>&1
|
* 0 1 * * * curl -s "https://your-domain.com/api/email_client/cronDailyCreateTasks" >> /var/log/promotion_cron.log 2>&1
|
||||||
*
|
|
||||||
* 配合已有定时任务:
|
|
||||||
* 22:00 prepareTasksForDate — 预渲染邮件内容
|
|
||||||
* 08:00 triggerTasksForDate — 开始发送
|
|
||||||
*/
|
*/
|
||||||
public function cronDailyCreateTasks()
|
public function cronDailyCreateTasks()
|
||||||
{
|
{
|
||||||
set_time_limit(120);
|
set_time_limit(120);
|
||||||
|
|
||||||
$sendDate = date('Y-m-d', strtotime('+1 day'));
|
$sendDate = date('Y-m-d', strtotime('+1 day'));
|
||||||
$dailyLimit = 100;
|
$noRepeatDays = 30;
|
||||||
$noRepeatDays = 7;
|
|
||||||
|
|
||||||
$journals = Db::name('journal')
|
$factories = Db::name('promotion_factory')
|
||||||
->where('start_promotion', 1)
|
->alias('f')
|
||||||
->where('state', 0)
|
->join('t_journal j', 'j.journal_id = f.journal_id', 'inner')
|
||||||
->where('default_template_id', '>', 0)
|
->where('f.state', 0)
|
||||||
|
->where('f.expert_type', 5)
|
||||||
|
->where('j.state', 0)
|
||||||
|
->where('j.start_promotion', 1)
|
||||||
|
->field('f.*, j.title as journal_title, j.default_template_id, j.default_style_id')
|
||||||
->select();
|
->select();
|
||||||
|
|
||||||
if (empty($journals)) {
|
if (empty($factories)) {
|
||||||
return jsonSuccess(['msg' => 'No journals with promotion enabled', 'created' => 0]);
|
return jsonSuccess(['msg' => 'No active factories found', 'created' => 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$created = 0;
|
$created = 0;
|
||||||
@@ -2160,13 +2156,12 @@ class EmailClient extends Base
|
|||||||
$errors = [];
|
$errors = [];
|
||||||
$details = [];
|
$details = [];
|
||||||
|
|
||||||
foreach ($journals as $journal) {
|
foreach ($factories as $factory) {
|
||||||
$journalId = $journal['journal_id'];
|
$factoryId = intval($factory['promotion_factory_id']);
|
||||||
$templateId = intval($journal['default_template_id']);
|
$journalId = intval($factory['journal_id']);
|
||||||
$styleId = intval(isset($journal['default_style_id']) ? $journal['default_style_id'] : 0);
|
|
||||||
|
|
||||||
$existTask = Db::name('promotion_task')
|
$existTask = Db::name('promotion_task')
|
||||||
->where('journal_id', $journalId)
|
->where('factory_id', $factoryId)
|
||||||
->where('send_date', $sendDate)
|
->where('send_date', $sendDate)
|
||||||
->where('state', 'in', [0, 1, 5])
|
->where('state', 'in', [0, 1, 5])
|
||||||
->find();
|
->find();
|
||||||
@@ -2175,31 +2170,57 @@ class EmailClient extends Base
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$templateId = intval($factory['template_id']) > 0
|
||||||
|
? intval($factory['template_id'])
|
||||||
|
: intval($factory['default_template_id']);
|
||||||
|
$styleId = intval($factory['style_id']) > 0
|
||||||
|
? intval($factory['style_id'])
|
||||||
|
: intval(isset($factory['default_style_id']) ? $factory['default_style_id'] : 0);
|
||||||
|
|
||||||
|
if ($templateId <= 0) {
|
||||||
|
$errors[] = 'factory_id=' . $factoryId . ': no template_id configured';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$tpl = Db::name('mail_template')
|
$tpl = Db::name('mail_template')
|
||||||
->where('template_id', $templateId)
|
->where('template_id', $templateId)
|
||||||
->where('journal_id', $journalId)
|
->where('journal_id', $journalId)
|
||||||
->where('state', 0)
|
->where('state', 0)
|
||||||
->find();
|
->find();
|
||||||
if (!$tpl) {
|
if (!$tpl) {
|
||||||
$errors[] = 'journal_id=' . $journalId . ': template_id=' . $templateId . ' not found';
|
$errors[] = 'factory_id=' . $factoryId . ': template_id=' . $templateId . ' not found';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$smtpIds = trim((string)$factory['email_ids']);
|
||||||
|
if ($smtpIds === '') {
|
||||||
$smtpCount = Db::name('journal_email')
|
$smtpCount = Db::name('journal_email')
|
||||||
->where('journal_id', $journalId)
|
->where('journal_id', $journalId)
|
||||||
->where('state', 0)
|
->where('state', 0)
|
||||||
->count();
|
->count();
|
||||||
if ($smtpCount == 0) {
|
if ($smtpCount == 0) {
|
||||||
$errors[] = 'journal_id=' . $journalId . ': no active SMTP account';
|
$errors[] = 'factory_id=' . $factoryId . ': no active SMTP account';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = $this->resolveFieldsByFetchIds($factory['fetch_ids']);
|
||||||
|
if (empty($fields)) {
|
||||||
|
$errors[] = 'factory_id=' . $factoryId . ': no valid fields from fetch_ids';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$experts = $this->findEligibleExperts($journal, $noRepeatDays, $dailyLimit);
|
$targetPartitions = trim((string)$factory['target_partitions']);
|
||||||
|
$targetCountryIds = trim((string)$factory['target_country_ids']);
|
||||||
|
$dailyLimit = max(1, intval($factory['send_count']));
|
||||||
|
|
||||||
|
$experts = $this->findEligibleExperts($fields, $noRepeatDays, $dailyLimit, $targetPartitions, $targetCountryIds);
|
||||||
|
|
||||||
if (empty($experts)) {
|
if (empty($experts)) {
|
||||||
$details[] = [
|
$details[] = [
|
||||||
|
'factory_id' => $factoryId,
|
||||||
'journal_id' => $journalId,
|
'journal_id' => $journalId,
|
||||||
'title' => $journal['title'],
|
'title' => $factory['journal_title'],
|
||||||
'status' => 'no_experts',
|
'status' => 'no_experts',
|
||||||
];
|
];
|
||||||
continue;
|
continue;
|
||||||
@@ -2209,16 +2230,19 @@ class EmailClient extends Base
|
|||||||
$scene = $tpl['scene'] ?? 'promotion';
|
$scene = $tpl['scene'] ?? 'promotion';
|
||||||
$taskId = Db::name('promotion_task')->insertGetId([
|
$taskId = Db::name('promotion_task')->insertGetId([
|
||||||
'journal_id' => $journalId,
|
'journal_id' => $journalId,
|
||||||
|
'factory_id' => $factoryId,
|
||||||
'template_id' => $templateId,
|
'template_id' => $templateId,
|
||||||
'style_id' => $styleId,
|
'style_id' => $styleId,
|
||||||
'scene' => $scene,
|
'scene' => $scene,
|
||||||
'task_name' => 'Auto-' . ($journal['title'] ?? $journalId) . '-' . $sendDate,
|
'type' => intval($factory['type']),
|
||||||
|
'expert_type' => intval($factory['expert_type']),
|
||||||
|
'task_name' => 'Auto-' . ($factory['journal_title'] ?? $journalId) . '-F' . $factoryId . '-' . $sendDate,
|
||||||
'total_count' => count($experts),
|
'total_count' => count($experts),
|
||||||
'sent_count' => 0,
|
'sent_count' => 0,
|
||||||
'fail_count' => 0,
|
'fail_count' => 0,
|
||||||
'bounce_count' => 0,
|
'bounce_count' => 0,
|
||||||
'state' => 0,
|
'state' => 0,
|
||||||
'smtp_ids' => '',
|
'smtp_ids' => $smtpIds,
|
||||||
'min_interval' => 30,
|
'min_interval' => 30,
|
||||||
'max_interval' => 60,
|
'max_interval' => 60,
|
||||||
'max_bounce_rate' => 5,
|
'max_bounce_rate' => 5,
|
||||||
@@ -2226,6 +2250,8 @@ class EmailClient extends Base
|
|||||||
'send_start_hour' => 8,
|
'send_start_hour' => 8,
|
||||||
'send_end_hour' => 22,
|
'send_end_hour' => 22,
|
||||||
'send_date' => $sendDate,
|
'send_date' => $sendDate,
|
||||||
|
'target_partitions' => $targetPartitions,
|
||||||
|
'target_country_ids' => $targetCountryIds,
|
||||||
'ctime' => $now,
|
'ctime' => $now,
|
||||||
'utime' => $now,
|
'utime' => $now,
|
||||||
]);
|
]);
|
||||||
@@ -2248,8 +2274,9 @@ class EmailClient extends Base
|
|||||||
|
|
||||||
$created++;
|
$created++;
|
||||||
$details[] = [
|
$details[] = [
|
||||||
|
'factory_id' => $factoryId,
|
||||||
'journal_id' => $journalId,
|
'journal_id' => $journalId,
|
||||||
'title' => $journal['title'],
|
'title' => $factory['journal_title'],
|
||||||
'task_id' => $taskId,
|
'task_id' => $taskId,
|
||||||
'expert_count' => count($experts),
|
'expert_count' => count($experts),
|
||||||
'send_date' => $sendDate,
|
'send_date' => $sendDate,
|
||||||
@@ -2325,12 +2352,31 @@ class EmailClient extends Base
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据期刊选定的推广领域(journal_promotion_field → expert_fetch)筛选合适的专家
|
* 根据 fetch_ids(逗号分隔的 expert_fetch_id)查出 field 名称数组
|
||||||
*/
|
*/
|
||||||
private function findEligibleExperts($journal, $noRepeatDays, $limit)
|
private function resolveFieldsByFetchIds($fetchIds)
|
||||||
{
|
{
|
||||||
$journalId = intval($journal['journal_id']);
|
$fetchIds = trim((string)$fetchIds);
|
||||||
|
if ($fetchIds === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$ids = array_map('intval', explode(',', $fetchIds));
|
||||||
|
$ids = array_filter($ids);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$fields = Db::name('expert_fetch')
|
||||||
|
->where('expert_fetch_id', 'in', $ids)
|
||||||
|
->where('state', 0)
|
||||||
|
->column('field');
|
||||||
|
return array_values(array_unique(array_filter(array_map('trim', $fields))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 journal_promotion_field 查出期刊绑定的领域名称数组(旧逻辑兼容)
|
||||||
|
*/
|
||||||
|
private function resolveFieldsByJournal($journalId)
|
||||||
|
{
|
||||||
$fields = Db::name('journal_promotion_field')
|
$fields = Db::name('journal_promotion_field')
|
||||||
->alias('jpf')
|
->alias('jpf')
|
||||||
->join('t_expert_fetch ef_fetch', 'ef_fetch.expert_fetch_id = jpf.expert_fetch_id', 'inner')
|
->join('t_expert_fetch ef_fetch', 'ef_fetch.expert_fetch_id = jpf.expert_fetch_id', 'inner')
|
||||||
@@ -2338,9 +2384,49 @@ class EmailClient extends Base
|
|||||||
->where('jpf.state', 0)
|
->where('jpf.state', 0)
|
||||||
->where('ef_fetch.state', 0)
|
->where('ef_fetch.state', 0)
|
||||||
->column('ef_fetch.field');
|
->column('ef_fetch.field');
|
||||||
|
return array_values(array_unique(array_filter(array_map('trim', $fields))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 target_partitions + target_country_ids 解析出最终要筛选的 country_id 列表
|
||||||
|
* 两者取并集;都为空则返回空数组,表示不做国家过滤
|
||||||
|
*/
|
||||||
|
private function resolveCountryIds($targetPartitions, $targetCountryIds)
|
||||||
|
{
|
||||||
|
$ids = [];
|
||||||
|
|
||||||
$fields = array_unique(array_filter(array_map('trim', $fields)));
|
if ($targetPartitions !== '') {
|
||||||
|
$parts = array_map('intval', explode(',', $targetPartitions));
|
||||||
|
$parts = array_filter($parts);
|
||||||
|
if (!empty($parts)) {
|
||||||
|
$partIds = Db::name('country')
|
||||||
|
->where('partition', 'in', $parts)
|
||||||
|
->column('country_id');
|
||||||
|
$ids = array_merge($ids, $partIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($targetCountryIds !== '') {
|
||||||
|
$extra = array_map('intval', explode(',', $targetCountryIds));
|
||||||
|
$ids = array_merge($ids, $extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_filter($ids)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据领域列表 + 国家筛选 + noRepeatDays 筛选合适的专家
|
||||||
|
*
|
||||||
|
* @param array $fields 领域名称数组(由调用方从 factory.fetch_ids 或 journal_promotion_field 查好传入)
|
||||||
|
* @param int $noRepeatDays 同一专家N天内不重复
|
||||||
|
* @param int $limit 最大返回数
|
||||||
|
* @param string $targetPartitions 国家分区,逗号分隔
|
||||||
|
* @param string $targetCountryIds 单独指定的 country_id,逗号分隔
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function findEligibleExperts($fields, $noRepeatDays, $limit, $targetPartitions = '', $targetCountryIds = '')
|
||||||
|
{
|
||||||
|
$fields = array_values(array_unique(array_filter(array_map('trim', $fields))));
|
||||||
|
|
||||||
if (empty($fields)) {
|
if (empty($fields)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -2368,6 +2454,11 @@ class EmailClient extends Base
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$countryIds = $this->resolveCountryIds($targetPartitions, $targetCountryIds);
|
||||||
|
if (!empty($countryIds)) {
|
||||||
|
$query->where('e.country_id', 'in', $countryIds);
|
||||||
|
}
|
||||||
|
|
||||||
return $query
|
return $query
|
||||||
->field('e.*')
|
->field('e.*')
|
||||||
->group('e.expert_id')
|
->group('e.expert_id')
|
||||||
|
|||||||
263
application/api/controller/PromotionFactory.php
Normal file
263
application/api/controller/PromotionFactory.php
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use think\Db;
|
||||||
|
|
||||||
|
class PromotionFactory extends Base
|
||||||
|
{
|
||||||
|
public function __construct(\think\Request $request = null)
|
||||||
|
{
|
||||||
|
parent::__construct($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工厂列表
|
||||||
|
* 参数: journal_id, state(-1=全部), page, per_page
|
||||||
|
*/
|
||||||
|
public function getList()
|
||||||
|
{
|
||||||
|
$journalId = intval($this->request->param('journal_id', 0));
|
||||||
|
$editor_id = $this->request->param("user_id",0);
|
||||||
|
$state = $this->request->param('state', '-1');
|
||||||
|
$page = max(1, intval($this->request->param('page', 1)));
|
||||||
|
$perPage = max(1, min(intval($this->request->param('per_page', 20)), 100));
|
||||||
|
|
||||||
|
// if (!$journalId) {
|
||||||
|
// return jsonError('journal_id is required');
|
||||||
|
// }
|
||||||
|
if($journalId!==0){
|
||||||
|
$where[] = ['journal_id', '=', $journalId];
|
||||||
|
}else{
|
||||||
|
$journalIds = $this->journal_obj->where("editor_id",$editor_id)->where("state",0)->column("journal_id");
|
||||||
|
$where[] = ['journal_id',"in",$journalIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state !== '-1' && $state !== '') {
|
||||||
|
$where[] = ['state', '=', intval($state)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$countQuery = Db::name('promotion_factory');
|
||||||
|
foreach ($where as $w) {
|
||||||
|
$countQuery->where($w[0], $w[1], $w[2]);
|
||||||
|
}
|
||||||
|
$total = $countQuery->count();
|
||||||
|
|
||||||
|
$listQuery = Db::name('promotion_factory');
|
||||||
|
foreach ($where as $w) {
|
||||||
|
$listQuery->where($w[0], $w[1], $w[2]);
|
||||||
|
}
|
||||||
|
$list = $listQuery
|
||||||
|
->order('promotion_factory_id desc')
|
||||||
|
->page($page, $perPage)
|
||||||
|
->select();
|
||||||
|
|
||||||
|
foreach ($list as &$item) {
|
||||||
|
$item['ctime_text'] = $item['ctime'] ? date('Y-m-d H:i:s', $item['ctime']) : '';
|
||||||
|
$item['fetch_fields'] = $this->resolveFetchFields($item['fetch_ids']);
|
||||||
|
$item['country_scope_label'] = $this->buildCountryScopeLabel(
|
||||||
|
isset($item['target_partitions']) ? (string)$item['target_partitions'] : '',
|
||||||
|
isset($item['target_country_ids']) ? (string)$item['target_country_ids'] : ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess([
|
||||||
|
'list' => $list,
|
||||||
|
'total' => $total,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => $perPage,
|
||||||
|
'total_pages' => $total > 0 ? ceil($total / $perPage) : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工厂详情
|
||||||
|
* 参数: promotion_factory_id
|
||||||
|
*/
|
||||||
|
public function getDetail()
|
||||||
|
{
|
||||||
|
$id = intval($this->request->param('promotion_factory_id', 0));
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('promotion_factory_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = Db::name('promotion_factory')->where('promotion_factory_id', $id)->find();
|
||||||
|
if (!$row) {
|
||||||
|
return jsonError('Factory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row['ctime_text'] = $row['ctime'] ? date('Y-m-d H:i:s', $row['ctime']) : '';
|
||||||
|
$row['fetch_fields'] = $this->resolveFetchFields($row['fetch_ids']);
|
||||||
|
$row['country_scope_label'] = $this->buildCountryScopeLabel(
|
||||||
|
isset($row['target_partitions']) ? (string)$row['target_partitions'] : '',
|
||||||
|
isset($row['target_country_ids']) ? (string)$row['target_country_ids'] : ''
|
||||||
|
);
|
||||||
|
|
||||||
|
return jsonSuccess($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增工厂
|
||||||
|
*/
|
||||||
|
public function add()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
|
||||||
|
$journalId = intval(isset($data['journal_id']) ? $data['journal_id'] : 0);
|
||||||
|
if (!$journalId) {
|
||||||
|
return jsonError('journal_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$journal = Db::name('journal')->where('journal_id', $journalId)->where('state', 0)->find();
|
||||||
|
if (!$journal) {
|
||||||
|
return jsonError('Journal not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$id = Db::name('promotion_factory')->insertGetId([
|
||||||
|
'journal_id' => $journalId,
|
||||||
|
'type' => intval(isset($data['type']) ? $data['type'] : 0),
|
||||||
|
'expert_type' => intval(isset($data['expert_type']) ? $data['expert_type'] : 5),
|
||||||
|
'email_ids' => trim(isset($data['email_ids']) ? (string)$data['email_ids'] : ''),
|
||||||
|
'send_count' => max(1, intval(isset($data['send_count']) ? $data['send_count'] : 100)),
|
||||||
|
'template_id' => intval(isset($data['template_id']) ? $data['template_id'] : 0),
|
||||||
|
'style_id' => intval(isset($data['style_id']) ? $data['style_id'] : 0),
|
||||||
|
'fetch_ids' => trim(isset($data['fetch_ids']) ? (string)$data['fetch_ids'] : ''),
|
||||||
|
'target_partitions' => trim(isset($data['target_partitions']) ? (string)$data['target_partitions'] : ''),
|
||||||
|
'target_country_ids' => trim(isset($data['target_country_ids']) ? (string)$data['target_country_ids'] : ''),
|
||||||
|
'state' => 0,
|
||||||
|
'ctime' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return jsonSuccess(['promotion_factory_id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑工厂
|
||||||
|
*/
|
||||||
|
public function edit()
|
||||||
|
{
|
||||||
|
$data = $this->request->post();
|
||||||
|
$id = intval(isset($data['promotion_factory_id']) ? $data['promotion_factory_id'] : 0);
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('promotion_factory_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = Db::name('promotion_factory')->where('promotion_factory_id', $id)->find();
|
||||||
|
if (!$row) {
|
||||||
|
return jsonError('Factory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$update = [];
|
||||||
|
$allowedFields = [
|
||||||
|
'type', 'expert_type', 'email_ids', 'send_count',
|
||||||
|
'template_id', 'style_id', 'fetch_ids',
|
||||||
|
'target_partitions', 'target_country_ids',
|
||||||
|
];
|
||||||
|
$intFields = ['type', 'expert_type', 'send_count', 'template_id', 'style_id'];
|
||||||
|
|
||||||
|
foreach ($allowedFields as $f) {
|
||||||
|
if (isset($data[$f])) {
|
||||||
|
$update[$f] = in_array($f, $intFields) ? intval($data[$f]) : trim((string)$data[$f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($update['send_count'])) {
|
||||||
|
$update['send_count'] = max(1, $update['send_count']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($update)) {
|
||||||
|
return jsonError('没有可更新的字段');
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('promotion_factory')->where('promotion_factory_id', $id)->update($update);
|
||||||
|
|
||||||
|
return jsonSuccess([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除工厂(软删除 state=1)
|
||||||
|
*/
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
$id = intval($this->request->param('promotion_factory_id', 0));
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('promotion_factory_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = Db::name('promotion_factory')->where('promotion_factory_id', $id)->find();
|
||||||
|
if (!$row) {
|
||||||
|
return jsonError('Factory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::name('promotion_factory')->where('promotion_factory_id', $id)->update(['state' => 1]);
|
||||||
|
|
||||||
|
return jsonSuccess([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用/禁用切换
|
||||||
|
*/
|
||||||
|
public function toggle()
|
||||||
|
{
|
||||||
|
$id = intval($this->request->param('promotion_factory_id', 0));
|
||||||
|
if (!$id) {
|
||||||
|
return jsonError('promotion_factory_id is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = Db::name('promotion_factory')->where('promotion_factory_id', $id)->find();
|
||||||
|
if (!$row) {
|
||||||
|
return jsonError('Factory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newState = ($row['state'] == 0) ? 1 : 0;
|
||||||
|
Db::name('promotion_factory')->where('promotion_factory_id', $id)->update(['state' => $newState]);
|
||||||
|
|
||||||
|
return jsonSuccess(['state' => $newState]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 fetch_ids 解析出领域名称列表
|
||||||
|
*/
|
||||||
|
private function resolveFetchFields($fetchIds)
|
||||||
|
{
|
||||||
|
$fetchIds = trim((string)$fetchIds);
|
||||||
|
if ($fetchIds === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$ids = array_map('intval', explode(',', $fetchIds));
|
||||||
|
$ids = array_filter($ids);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Db::name('expert_fetch')
|
||||||
|
->where('expert_fetch_id', 'in', $ids)
|
||||||
|
->column('field', 'expert_fetch_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建国家范围描述标签
|
||||||
|
*/
|
||||||
|
private function buildCountryScopeLabel($targetPartitions, $targetCountryIds)
|
||||||
|
{
|
||||||
|
$tp = trim($targetPartitions);
|
||||||
|
$tc = trim($targetCountryIds);
|
||||||
|
|
||||||
|
if ($tp === '' && $tc === '') {
|
||||||
|
return '全部国家';
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = [];
|
||||||
|
if ($tp !== '') {
|
||||||
|
$parts[] = '分区' . $tp;
|
||||||
|
}
|
||||||
|
if ($tc !== '') {
|
||||||
|
$cids = array_map('intval', explode(',', $tc));
|
||||||
|
$names = Db::name('country')->where('country_id', 'in', $cids)->column('zh_name');
|
||||||
|
if (!empty($names)) {
|
||||||
|
$parts[] = implode(',', $names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return implode(' + ', $parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
sql/add_country_scope_to_promotion_task.sql
Normal file
3
sql/add_country_scope_to_promotion_task.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE t_promotion_task
|
||||||
|
ADD COLUMN target_partitions VARCHAR(32) NOT NULL DEFAULT '' COMMENT '目标分区,逗号分隔,如 1,2,3;空=不限',
|
||||||
|
ADD COLUMN target_country_ids VARCHAR(255) NOT NULL DEFAULT '' COMMENT '单独指定的国家ID,逗号分隔;空=不额外指定';
|
||||||
Reference in New Issue
Block a user