修改自动推广的相关任务
This commit is contained in:
@@ -5,6 +5,7 @@ namespace app\common;
|
||||
use think\Db;
|
||||
use think\Cache;
|
||||
use think\Queue;
|
||||
use think\Env;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
class PromotionService
|
||||
@@ -61,30 +62,83 @@ class PromotionService
|
||||
return ['done' => true, 'reason' => 'all_emails_processed'];
|
||||
}
|
||||
|
||||
$expert = Db::name('expert')->where('expert_id', $logEntry['expert_id'])->find();
|
||||
if (!$expert || $expert['state'] == 4 || $expert['state'] == 5) {
|
||||
// 受众分发:log.expert_id>0 → 外部 expert 库;log.user_id>0 → 系统内部用户
|
||||
$audienceKind = '';
|
||||
$expert = null;
|
||||
|
||||
if (intval($logEntry['expert_id']) > 0) {
|
||||
$audienceKind = 'expert';
|
||||
$expert = Db::name('expert')->where('expert_id', $logEntry['expert_id'])->find();
|
||||
if (!$expert || $expert['state'] == 4 || $expert['state'] == 5) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert invalid or deleted (state=' . (isset($expert['state']) ? $expert['state'] : 'null') . ')',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('fail_count');
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'expert_invalid'];
|
||||
}
|
||||
if (!empty($expert['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'expert_unsubscribed'];
|
||||
}
|
||||
} elseif (intval($logEntry['user_id']) > 0) {
|
||||
$audienceKind = 'user';
|
||||
$row = Db::name('user')->alias('u')
|
||||
->join('t_user_reviewer_info uri', 'uri.reviewer_id = u.user_id', 'left')
|
||||
->where('u.user_id', $logEntry['user_id'])
|
||||
->field('u.user_id, u.email, u.realname, u.unsubscribed, IFNULL(uri.company, "") as company')
|
||||
->find();
|
||||
if (!$row) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'User not found',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('fail_count');
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'user_invalid'];
|
||||
}
|
||||
if (!empty($row['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'User unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'user_unsubscribed'];
|
||||
}
|
||||
$expert = [
|
||||
'expert_id' => 0,
|
||||
'user_id' => intval($row['user_id']),
|
||||
'name' => (string)$row['realname'],
|
||||
'email' => (string)$row['email'],
|
||||
'affiliation' => (string)$row['company'],
|
||||
];
|
||||
} else {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert invalid or deleted (state=' . (isset($expert['state']) ? $expert['state'] : 'null') . ')',
|
||||
'error_msg' => 'No audience id (expert_id/user_id both empty)',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('fail_count');
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'expert_invalid'];
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'no_audience_id'];
|
||||
}
|
||||
|
||||
// 退订过滤(防止准备 → 发送之间窗口期内退订的人被误发)
|
||||
if (!empty($expert['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update(['utime' => time()]);
|
||||
$this->enqueueNextEmail($taskId, 2);
|
||||
return ['done' => false, 'skipped' => $logEntry['email_to'], 'reason' => 'expert_unsubscribed'];
|
||||
}
|
||||
// 注入 role(供模板 {{role}} 使用)
|
||||
$expert['role'] = $this->mapExpertTypeRole(intval($task['expert_type'] ?? 0));
|
||||
|
||||
$account = $this->pickSmtpAccountForTask($task);
|
||||
if (!$account) {
|
||||
@@ -101,49 +155,64 @@ class PromotionService
|
||||
$subject = $logEntry['subject_prepared'];
|
||||
$body = $logEntry['body_prepared'];
|
||||
} else {
|
||||
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
||||
$expert_fields = Db::name('expert_field')
|
||||
->where('expert_id', $expert['expert_id'])
|
||||
->where('state', 0)
|
||||
->order('expert_field_id desc')
|
||||
->select();
|
||||
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
||||
|
||||
$taskFields = $this->resolveTaskFields($task);
|
||||
$taskFieldLower = [];
|
||||
foreach ($taskFields as $tf) {
|
||||
$tf = strtolower(trim($tf));
|
||||
if ($tf !== '') $taskFieldLower[$tf] = true;
|
||||
}
|
||||
// 邀请青年编委申请链接(同 prepareSingleEmail 路径,保持模板变量一致)
|
||||
$expert['application_link_yeditorial_board'] = self::buildYboardApplyUrl(
|
||||
intval($expert['expert_id'] ?? 0),
|
||||
intval($task['journal_id'])
|
||||
);
|
||||
|
||||
$fieldSet = [];
|
||||
$matchedTitle = '';
|
||||
$fallbackTitle = '';
|
||||
foreach ($expert_fields as $ef) {
|
||||
$fn = trim($ef['field']);
|
||||
if ($fn !== '' && !in_array($fn, $fieldSet)) {
|
||||
$fieldSet[] = $fn;
|
||||
}
|
||||
$paper = trim((string)($ef['paper_title'] ?? ''));
|
||||
if ($paper === '') continue;
|
||||
if ($matchedTitle === '' && $fn !== '' && isset($taskFieldLower[strtolower($fn)])) {
|
||||
$matchedTitle = $paper;
|
||||
}
|
||||
if ($fallbackTitle === '') {
|
||||
$fallbackTitle = $paper;
|
||||
}
|
||||
if ($matchedTitle !== '' && $fallbackTitle !== '') break;
|
||||
}
|
||||
$expert['fields'] = implode(',', $fieldSet);
|
||||
$expert['representative_work_title'] = $matchedTitle !== '' ? $matchedTitle : $fallbackTitle;
|
||||
if ($audienceKind === 'expert') {
|
||||
$expert_fields = Db::name('expert_field')
|
||||
->where('expert_id', $expert['expert_id'])
|
||||
->where('state', 0)
|
||||
->order('expert_field_id desc')
|
||||
->select();
|
||||
|
||||
// 现场发送路径:没有提前准备 LLM,退回 .env 兜底文案
|
||||
try {
|
||||
$llmSvc = new PromotionLlmService();
|
||||
$expert['llm_description'] = $llmSvc->getFallback();
|
||||
$expert['ai_advised_topics'] = $llmSvc->getAdvisedFallback();
|
||||
} catch (\Exception $e) {
|
||||
$expert['llm_description'] = '';
|
||||
$expert['ai_advised_topics'] = '';
|
||||
$taskFields = $this->resolveTaskFields($task);
|
||||
$taskFieldLower = [];
|
||||
foreach ($taskFields as $tf) {
|
||||
$tf = strtolower(trim($tf));
|
||||
if ($tf !== '') $taskFieldLower[$tf] = true;
|
||||
}
|
||||
|
||||
$fieldSet = [];
|
||||
$matchedTitle = '';
|
||||
$fallbackTitle = '';
|
||||
foreach ($expert_fields as $ef) {
|
||||
$fn = trim($ef['field']);
|
||||
if ($fn !== '' && !in_array($fn, $fieldSet)) {
|
||||
$fieldSet[] = $fn;
|
||||
}
|
||||
$paper = trim((string)($ef['paper_title'] ?? ''));
|
||||
if ($paper === '') continue;
|
||||
if ($matchedTitle === '' && $fn !== '' && isset($taskFieldLower[strtolower($fn)])) {
|
||||
$matchedTitle = $paper;
|
||||
}
|
||||
if ($fallbackTitle === '') {
|
||||
$fallbackTitle = $paper;
|
||||
}
|
||||
if ($matchedTitle !== '' && $fallbackTitle !== '') break;
|
||||
}
|
||||
$expert['fields'] = implode(',', $fieldSet);
|
||||
$expert['representative_work_title'] = $matchedTitle !== '' ? $matchedTitle : $fallbackTitle;
|
||||
|
||||
// 现场发送路径:没有提前准备 LLM,退回 .env 兜底文案
|
||||
try {
|
||||
$llmSvc = new PromotionLlmService();
|
||||
$expert['llm_description'] = $llmSvc->getFallback();
|
||||
$expert['ai_advised_topics'] = $llmSvc->getAdvisedFallback();
|
||||
} catch (\Exception $e) {
|
||||
$expert['llm_description'] = '';
|
||||
$expert['ai_advised_topics'] = '';
|
||||
}
|
||||
} else {
|
||||
// 内部受众:领域 / 代表作 / LLM 全部跳过
|
||||
$expert['fields'] = '';
|
||||
$expert['representative_work_title'] = '';
|
||||
$expert['llm_description'] = '';
|
||||
$expert['ai_advised_topics'] = '';
|
||||
}
|
||||
|
||||
$expertVars = $this->buildExpertVars($expert);
|
||||
@@ -184,7 +253,10 @@ class PromotionService
|
||||
'send_time' => $now,
|
||||
]);
|
||||
Db::name('journal_email')->where('j_email_id', $account['j_email_id'])->setInc('today_sent');
|
||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['state' => 1, 'ltime' => $now]);
|
||||
// 仅外部 expert 库回写最近一次推广时间;内部 user 用 promotion_email_log.send_time 计频次
|
||||
if ($audienceKind === 'expert' && intval($expert['expert_id']) > 0) {
|
||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['state' => 1, 'ltime' => $now]);
|
||||
}
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->setInc('sent_count');
|
||||
} else {
|
||||
Db::name('promotion_email_log')->where('log_id', $logEntry['log_id'])->update([
|
||||
@@ -287,117 +359,186 @@ class PromotionService
|
||||
return ['code' => 1, 'msg' => 'task_not_found', 'llm_status' => 0];
|
||||
}
|
||||
|
||||
$expert = Db::name('expert')->where('expert_id', $log['expert_id'])->find();
|
||||
if (!$expert) {
|
||||
// 受众分发:log.expert_id>0 → 外部 expert 库;log.user_id>0 → 系统内部用户(编委/主编/...)
|
||||
$expertType = intval($task['expert_type'] ?? 0);
|
||||
$audienceKind = '';
|
||||
$expert = null;
|
||||
|
||||
if (intval($log['expert_id']) > 0) {
|
||||
$audienceKind = 'expert';
|
||||
$expert = Db::name('expert')->where('expert_id', $log['expert_id'])->find();
|
||||
if (!$expert) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert not found',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'expert_not_found', 'llm_status' => 0];
|
||||
}
|
||||
if (!empty($expert['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'expert_unsubscribed', 'llm_status' => 0];
|
||||
}
|
||||
} elseif (intval($log['user_id']) > 0) {
|
||||
$audienceKind = 'user';
|
||||
$row = Db::name('user')->alias('u')
|
||||
->join('t_user_reviewer_info uri', 'uri.reviewer_id = u.user_id', 'left')
|
||||
->where('u.user_id', $log['user_id'])
|
||||
->field('u.user_id, u.email, u.realname, u.unsubscribed, IFNULL(uri.company, "") as company')
|
||||
->find();
|
||||
if (!$row) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'User not found',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'user_not_found', 'llm_status' => 0];
|
||||
}
|
||||
if (!empty($row['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'User unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'user_unsubscribed', 'llm_status' => 0];
|
||||
}
|
||||
// 对齐外部 expert 数据结构,buildExpertVars 直接复用
|
||||
$expert = [
|
||||
'expert_id' => 0,
|
||||
'user_id' => intval($row['user_id']),
|
||||
'name' => (string)$row['realname'],
|
||||
'email' => (string)$row['email'],
|
||||
'affiliation' => (string)$row['company'],
|
||||
];
|
||||
} else {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert not found',
|
||||
'error_msg' => 'No audience id (expert_id/user_id both empty)',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'expert_not_found', 'llm_status' => 0];
|
||||
}
|
||||
if (!empty($expert['unsubscribed'])) {
|
||||
Db::name('promotion_email_log')->where('log_id', $logId)->update([
|
||||
'state' => 2,
|
||||
'error_msg' => 'Expert unsubscribed',
|
||||
'send_time' => time(),
|
||||
]);
|
||||
$this->tryFinalizeTask($task['task_id']);
|
||||
return ['code' => 1, 'msg' => 'expert_unsubscribed', 'llm_status' => 0];
|
||||
}
|
||||
|
||||
$expert_fields = Db::name('expert_field')
|
||||
->where('expert_id', $expert['expert_id'])
|
||||
->where('state', 0)
|
||||
->order('expert_field_id desc')
|
||||
->select();
|
||||
|
||||
// 领域优先级:任务所属工厂/期刊的目标领域
|
||||
$taskFields = $this->resolveTaskFields($task);
|
||||
$taskFieldLower = [];
|
||||
foreach ($taskFields as $tf) {
|
||||
$tf = strtolower(trim($tf));
|
||||
if ($tf !== '') $taskFieldLower[$tf] = true;
|
||||
}
|
||||
|
||||
$fieldSet = [];
|
||||
$matchedTitle = '';
|
||||
$fallbackTitle = '';
|
||||
foreach ($expert_fields as $ef) {
|
||||
$fn = trim($ef['field']);
|
||||
if ($fn !== '' && !in_array($fn, $fieldSet)) {
|
||||
$fieldSet[] = $fn;
|
||||
}
|
||||
$paper = trim((string)($ef['paper_title'] ?? ''));
|
||||
if ($paper === '') continue;
|
||||
|
||||
// 匹配到任务领域的 paper 优先(按最新 expert_field_id desc 顺序取第一条)
|
||||
if ($matchedTitle === '' && $fn !== '' && isset($taskFieldLower[strtolower($fn)])) {
|
||||
$matchedTitle = $paper;
|
||||
}
|
||||
if ($fallbackTitle === '') {
|
||||
$fallbackTitle = $paper;
|
||||
}
|
||||
if ($matchedTitle !== '' && $fallbackTitle !== '') break;
|
||||
}
|
||||
$expert['fields'] = implode(',', $fieldSet);
|
||||
$expert['representative_work_title'] = $matchedTitle !== '' ? $matchedTitle : $fallbackTitle;
|
||||
|
||||
// 领域交集(大小写不敏感,保留 expert 原字面值用于展示)
|
||||
$overlapFields = [];
|
||||
if (!empty($taskFieldLower)) {
|
||||
foreach ($fieldSet as $fn) {
|
||||
if (isset($taskFieldLower[strtolower($fn)])) {
|
||||
$overlapFields[] = $fn;
|
||||
}
|
||||
}
|
||||
return ['code' => 1, 'msg' => 'no_audience_id', 'llm_status' => 0];
|
||||
}
|
||||
|
||||
$journal = Db::name('journal')->where('journal_id', $task['journal_id'])->find();
|
||||
|
||||
// 一次 LLM 调用生成两段内容(description + advised_topics)
|
||||
$llmResult = [
|
||||
'description' => '',
|
||||
'description_status' => 0,
|
||||
'advised_topics' => '',
|
||||
'advised_topics_status' => 0,
|
||||
];
|
||||
try {
|
||||
$llm = new PromotionLlmService();
|
||||
$llmResult = $llm->generateEmailContent(
|
||||
$expert,
|
||||
$journal ?: [],
|
||||
$overlapFields,
|
||||
$taskFields,
|
||||
$fieldSet
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
// 兜底双占位($llm 实例可能未成功构建,单独拿一个尝试)
|
||||
$fbDesc = '';
|
||||
$fbAdvised = '';
|
||||
try {
|
||||
$fbSvc = isset($llm) ? $llm : new PromotionLlmService();
|
||||
$fbDesc = $fbSvc->getFallback();
|
||||
$fbAdvised = $fbSvc->getAdvisedFallback();
|
||||
} catch (\Exception $ignore) {
|
||||
// 忽略,使用空串
|
||||
}
|
||||
$llmResult = [
|
||||
'description' => $fbDesc,
|
||||
'description_status' => 2,
|
||||
'advised_topics' => $fbAdvised,
|
||||
'advised_topics_status' => 2,
|
||||
];
|
||||
$this->log("prepareSingleEmail log_id={$logId} llm_exception=" . $e->getMessage());
|
||||
}
|
||||
$llmText = (string)$llmResult['description'];
|
||||
$llmStatus = intval($llmResult['description_status']);
|
||||
$advisedText = (string)$llmResult['advised_topics'];
|
||||
$advisedStatus = intval($llmResult['advised_topics_status']);
|
||||
// 邀请青年编委申请链接(无 expert_id / 未配 APPLY_URL 时为空串,模板自行处理)
|
||||
$expert['application_link_yeditorial_board'] = self::buildYboardApplyUrl(
|
||||
intval($expert['expert_id'] ?? 0),
|
||||
intval($task['journal_id'])
|
||||
);
|
||||
|
||||
$expert['llm_description'] = $llmText;
|
||||
$expert['ai_advised_topics'] = $advisedText;
|
||||
// 内部受众:跳过 LLM;representative_work_title / fields 给空串占位,模板侧不引用即可
|
||||
if ($audienceKind === 'user') {
|
||||
$expert['fields'] = '';
|
||||
$expert['representative_work_title'] = '';
|
||||
$expert['llm_description'] = '';
|
||||
$expert['ai_advised_topics'] = '';
|
||||
$expert['role'] = $this->mapExpertTypeRole($expertType);
|
||||
|
||||
$llmText = '';
|
||||
$llmStatus = 0;
|
||||
$advisedText = '';
|
||||
$advisedStatus = 0;
|
||||
} else {
|
||||
// 外部 expert 库:领域 + 代表作 + LLM 完整流程
|
||||
$expert_fields = Db::name('expert_field')
|
||||
->where('expert_id', $expert['expert_id'])
|
||||
->where('state', 0)
|
||||
->order('expert_field_id desc')
|
||||
->select();
|
||||
|
||||
// 领域优先级:任务所属工厂/期刊的目标领域
|
||||
$taskFields = $this->resolveTaskFields($task);
|
||||
$taskFieldLower = [];
|
||||
foreach ($taskFields as $tf) {
|
||||
$tf = strtolower(trim($tf));
|
||||
if ($tf !== '') $taskFieldLower[$tf] = true;
|
||||
}
|
||||
|
||||
$fieldSet = [];
|
||||
$matchedTitle = '';
|
||||
$fallbackTitle = '';
|
||||
foreach ($expert_fields as $ef) {
|
||||
$fn = trim($ef['field']);
|
||||
if ($fn !== '' && !in_array($fn, $fieldSet)) {
|
||||
$fieldSet[] = $fn;
|
||||
}
|
||||
$paper = trim((string)($ef['paper_title'] ?? ''));
|
||||
if ($paper === '') continue;
|
||||
|
||||
// 匹配到任务领域的 paper 优先(按最新 expert_field_id desc 顺序取第一条)
|
||||
if ($matchedTitle === '' && $fn !== '' && isset($taskFieldLower[strtolower($fn)])) {
|
||||
$matchedTitle = $paper;
|
||||
}
|
||||
if ($fallbackTitle === '') {
|
||||
$fallbackTitle = $paper;
|
||||
}
|
||||
if ($matchedTitle !== '' && $fallbackTitle !== '') break;
|
||||
}
|
||||
$expert['fields'] = implode(',', $fieldSet);
|
||||
$expert['representative_work_title'] = $matchedTitle !== '' ? $matchedTitle : $fallbackTitle;
|
||||
|
||||
// 领域交集(大小写不敏感,保留 expert 原字面值用于展示)
|
||||
$overlapFields = [];
|
||||
if (!empty($taskFieldLower)) {
|
||||
foreach ($fieldSet as $fn) {
|
||||
if (isset($taskFieldLower[strtolower($fn)])) {
|
||||
$overlapFields[] = $fn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 一次 LLM 调用生成两段内容(description + advised_topics)
|
||||
$llmResult = [
|
||||
'description' => '',
|
||||
'description_status' => 0,
|
||||
'advised_topics' => '',
|
||||
'advised_topics_status' => 0,
|
||||
];
|
||||
try {
|
||||
$llm = new PromotionLlmService();
|
||||
$llmResult = $llm->generateEmailContent(
|
||||
$expert,
|
||||
$journal ?: [],
|
||||
$overlapFields,
|
||||
$taskFields,
|
||||
$fieldSet
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$fbDesc = '';
|
||||
$fbAdvised = '';
|
||||
try {
|
||||
$fbSvc = isset($llm) ? $llm : new PromotionLlmService();
|
||||
$fbDesc = $fbSvc->getFallback();
|
||||
$fbAdvised = $fbSvc->getAdvisedFallback();
|
||||
} catch (\Exception $ignore) {
|
||||
}
|
||||
$llmResult = [
|
||||
'description' => $fbDesc,
|
||||
'description_status' => 2,
|
||||
'advised_topics' => $fbAdvised,
|
||||
'advised_topics_status' => 2,
|
||||
];
|
||||
$this->log("prepareSingleEmail log_id={$logId} llm_exception=" . $e->getMessage());
|
||||
}
|
||||
$llmText = (string)$llmResult['description'];
|
||||
$llmStatus = intval($llmResult['description_status']);
|
||||
$advisedText = (string)$llmResult['advised_topics'];
|
||||
$advisedStatus = intval($llmResult['advised_topics_status']);
|
||||
|
||||
$expert['llm_description'] = $llmText;
|
||||
$expert['ai_advised_topics'] = $advisedText;
|
||||
$expert['role'] = $this->mapExpertTypeRole($expertType);
|
||||
}
|
||||
|
||||
$expertVars = $this->buildExpertVars($expert);
|
||||
$journalVars = $this->buildJournalVars($journal);
|
||||
@@ -814,12 +955,15 @@ class PromotionService
|
||||
$llm = $expert['llm_description'] ?? '';
|
||||
$advised = $expert['ai_advised_topics'] ?? '';
|
||||
|
||||
// 退订 URL:根据受众身份选择 kind=expert / user
|
||||
$unsubUrl = '';
|
||||
if (!empty($expert['expert_id']) && !empty($expert['email'])) {
|
||||
$unsubUrl = \app\common\UnsubscribeService::buildUrl(
|
||||
intval($expert['expert_id']),
|
||||
(string)$expert['email']
|
||||
);
|
||||
$email = (string)($expert['email'] ?? '');
|
||||
if ($email !== '') {
|
||||
if (!empty($expert['expert_id'])) {
|
||||
$unsubUrl = \app\common\UnsubscribeService::buildUrl('expert', intval($expert['expert_id']), $email);
|
||||
} elseif (!empty($expert['user_id'])) {
|
||||
$unsubUrl = \app\common\UnsubscribeService::buildUrl('user', intval($expert['user_id']), $email);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -829,14 +973,57 @@ class PromotionService
|
||||
'expert_affiliation' => $expert['affiliation'] ?? '',
|
||||
'expert_field' => $expert['fields'] ?? ($expert['field'] ?? ''),
|
||||
'representative_work_title' => $expert['representative_work_title'] ?? '',
|
||||
'role' => $expert['role'] ?? '',
|
||||
'llm_description' => $llm,
|
||||
'ai_content_analysis' => $llm,
|
||||
'ai_advised_topics' => $advised,
|
||||
'llm_advised_topics' => $advised,
|
||||
'unsubscribe_url' => $unsubUrl,
|
||||
'application_link_yeditorial_board' => $expert['application_link_yeditorial_board'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造"邀请青年编委申请"链接,供模板 {{application_link_yeditorial_board}} 使用。
|
||||
*
|
||||
* .env 配置([yboard] 段):
|
||||
* APPLY_URL=https://your-domain.com/yboard/apply
|
||||
*
|
||||
* 输出格式:
|
||||
* {APPLY_URL}?journal_id=X&expert_id=Y
|
||||
* (若 APPLY_URL 已带 ? 参数则用 & 续接)
|
||||
*
|
||||
* 任意参数无效时返回空串,模板侧不会渲染出非法链接。
|
||||
*/
|
||||
public static function buildYboardApplyUrl($expertId, $journalId)
|
||||
{
|
||||
$expertId = intval($expertId);
|
||||
$journalId = intval($journalId);
|
||||
if ($expertId <= 0 || $journalId <= 0) return '';
|
||||
|
||||
$base = trim((string)Env::get('yboard.apply_url', ''));
|
||||
if ($base === '') return '';
|
||||
|
||||
$sep = strpos($base, '?') === false ? '?' : '&';
|
||||
return $base . $sep . 'journal_id=' . $journalId . '&expert_id=' . $expertId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 expert_type 映射成英文角色文案,供模板 {{role}} 使用
|
||||
*/
|
||||
public function mapExpertTypeRole($expertType)
|
||||
{
|
||||
$map = [
|
||||
1 => 'Editor-in-Chief',
|
||||
2 => 'Editorial Board Member',
|
||||
3 => 'Young Editorial Board Member',
|
||||
4 => 'Author',
|
||||
5 => '',
|
||||
];
|
||||
$expertType = intval($expertType);
|
||||
return isset($map[$expertType]) ? $map[$expertType] : '';
|
||||
}
|
||||
|
||||
public function buildJournalVars($journal)
|
||||
{
|
||||
if (!$journal) return [];
|
||||
|
||||
Reference in New Issue
Block a user