自动推广
This commit is contained in:
@@ -4,8 +4,11 @@ namespace app\api\controller;
|
||||
|
||||
use think\Db;
|
||||
use think\Env;
|
||||
use PHPMailer\PHPMailer;
|
||||
use think\Cache;
|
||||
use think\Queue;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use think\Validate;
|
||||
use app\common\PromotionService;
|
||||
|
||||
class EmailClient extends Base
|
||||
{
|
||||
@@ -173,7 +176,10 @@ class EmailClient extends Base
|
||||
|
||||
/**
|
||||
* Send a single email using a specific journal's SMTP
|
||||
* Params: journal_id, to_email, subject, content, j_email_id(optional, auto-select if empty)
|
||||
* Params:
|
||||
* - journal_id, to_email
|
||||
* - subject/content OR template_id+vars_json
|
||||
* - optional: style_id (mail_style) and j_email_id (specific SMTP account)
|
||||
*/
|
||||
public function sendOne()
|
||||
{
|
||||
@@ -181,10 +187,26 @@ class EmailClient extends Base
|
||||
$toEmail = trim($this->request->param('to_email', ''));
|
||||
$subject = trim($this->request->param('subject', ''));
|
||||
$content = $this->request->param('content', '');
|
||||
$templateId = intval($this->request->param('template_id', 0));
|
||||
$varsJson = $this->request->param('vars_json', '');
|
||||
$styleId = intval($this->request->param('style_id', 0));
|
||||
$accountId = intval($this->request->param('j_email_id', 0));
|
||||
|
||||
if (!$journalId || empty($toEmail) || empty($subject) || empty($content)) {
|
||||
return jsonError('journal_id, to_email, subject, content are required');
|
||||
if (!$journalId || empty($toEmail)) {
|
||||
return jsonError('journal_id and to_email are required');
|
||||
}
|
||||
|
||||
if ($templateId) {
|
||||
$rendered = $this->renderFromTemplate($templateId, $journalId, $varsJson, $styleId);
|
||||
if ($rendered['code'] !== 0) {
|
||||
return jsonError($rendered['msg']);
|
||||
}
|
||||
$subject = $rendered['data']['subject'];
|
||||
$content = $rendered['data']['body'];
|
||||
} else {
|
||||
if (empty($subject) || empty($content)) {
|
||||
return jsonError('subject and content are required when template_id is not provided');
|
||||
}
|
||||
}
|
||||
|
||||
if ($accountId) {
|
||||
@@ -227,18 +249,27 @@ class EmailClient extends Base
|
||||
|
||||
/**
|
||||
* Send batch emails to multiple experts
|
||||
* Params: journal_id, expert_ids (comma separated), subject, content
|
||||
* Content supports variables: {name}, {email}, {affiliation}
|
||||
* Params: journal_id, expert_ids (comma separated),
|
||||
* - either subject + content
|
||||
* - or template_id (recommended), with optional style_id
|
||||
*
|
||||
* Supported variables in subject/content or template:
|
||||
* {name}/{email}/{affiliation}/{field} and {{name}}/{{email}}/... (both styles)
|
||||
*/
|
||||
public function sendBatch()
|
||||
{
|
||||
$journalId = intval($this->request->param('journal_id', 0));
|
||||
$expertIds = trim($this->request->param('expert_ids', ''));
|
||||
$subject = trim($this->request->param('subject', ''));
|
||||
$content = $this->request->param('content', '');
|
||||
$subject = trim($this->request->param('subject', ''));
|
||||
$content = $this->request->param('content', '');
|
||||
$templateId = intval($this->request->param('template_id', 0));
|
||||
$styleId = intval($this->request->param('style_id', 0));
|
||||
|
||||
if (!$journalId || empty($expertIds) || empty($subject) || empty($content)) {
|
||||
return jsonError('journal_id, expert_ids, subject, content are required');
|
||||
if (!$journalId || empty($expertIds)) {
|
||||
return jsonError('journal_id and expert_ids are required');
|
||||
}
|
||||
if (!$templateId && (empty($subject) || empty($content))) {
|
||||
return jsonError('subject and content are required when template_id is not provided');
|
||||
}
|
||||
|
||||
$ids = array_map('intval', explode(',', $expertIds));
|
||||
@@ -253,6 +284,9 @@ class EmailClient extends Base
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
|
||||
$journal = Db::name('journal')->where('journal_id', $journalId)->find();
|
||||
$journalVars = $this->buildJournalVars($journal);
|
||||
|
||||
foreach ($experts as $expert) {
|
||||
$account = $this->pickSmtpAccount($journalId);
|
||||
if (!$account) {
|
||||
@@ -261,14 +295,28 @@ class EmailClient extends Base
|
||||
break;
|
||||
}
|
||||
|
||||
$personalContent = $this->replaceVariables($content, $expert);
|
||||
$personalSubject = $this->replaceVariables($subject, $expert);
|
||||
$expertVars = $this->buildExpertVars($expert);
|
||||
$vars = array_merge($journalVars, $expertVars);
|
||||
|
||||
if ($templateId) {
|
||||
$rendered = $this->renderFromTemplate($templateId, $journalId, json_encode($vars, JSON_UNESCAPED_UNICODE), $styleId);
|
||||
if ($rendered['code'] !== 0) {
|
||||
$failed++;
|
||||
$errors[] = $expert['email'] . ': ' . $rendered['msg'];
|
||||
continue;
|
||||
}
|
||||
$personalSubject = $rendered['data']['subject'];
|
||||
$personalContent = $rendered['data']['body'];
|
||||
} else {
|
||||
$personalContent = $this->renderVars($content, $vars);
|
||||
$personalSubject = $this->renderVars($subject, $vars);
|
||||
}
|
||||
|
||||
$result = $this->doSendEmail($account, $expert['email'], $personalSubject, $personalContent);
|
||||
|
||||
if ($result['status'] === 1) {
|
||||
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]);
|
||||
Db::name('expert')->where('expert_id', $expert['expert_id'])->update(['state' => 1, 'ltime' => time()]);
|
||||
$sent++;
|
||||
} else {
|
||||
$failed++;
|
||||
@@ -1023,6 +1071,404 @@ class EmailClient extends Base
|
||||
return '';
|
||||
}
|
||||
|
||||
// ==================== Promotion Tasks ====================
|
||||
|
||||
/**
|
||||
* Create a promotion sending task
|
||||
* Params:
|
||||
* - journal_id, template_id, style_id, scene, task_name
|
||||
* - expert_ids (comma separated) OR field + major_id (auto-query from DB)
|
||||
* - smtp_ids (comma separated, optional: restrict to specific SMTP accounts)
|
||||
* - min_interval, max_interval (seconds between emails)
|
||||
* - max_bounce_rate (%), no_repeat_days
|
||||
* - send_start_hour, send_end_hour (UTC, default 8-22)
|
||||
*/
|
||||
public function createTask()
|
||||
{
|
||||
$journalId = intval($this->request->param('journal_id', 0));
|
||||
$templateId = intval($this->request->param('template_id', 0));
|
||||
$styleId = intval($this->request->param('style_id', 0));
|
||||
$scene = trim($this->request->param('scene', ''));
|
||||
$taskName = trim($this->request->param('task_name', ''));
|
||||
$expertIds = trim($this->request->param('expert_ids', ''));
|
||||
$field = trim($this->request->param('field', ''));
|
||||
$majorId = intval($this->request->param('major_id', 0));
|
||||
$smtpIds = trim($this->request->param('smtp_ids', ''));
|
||||
$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));
|
||||
$sendStartHour = intval($this->request->param('send_start_hour', 8));
|
||||
$sendEndHour = intval($this->request->param('send_end_hour', 22));
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
$tpl = Db::name('mail_template')
|
||||
->where('template_id', $templateId)
|
||||
->where('journal_id', $journalId)
|
||||
->where('state', 0)
|
||||
->find();
|
||||
if (!$tpl) {
|
||||
return jsonError('Template not found for this journal');
|
||||
}
|
||||
|
||||
if (empty($scene)) {
|
||||
$scene = $tpl['scene'];
|
||||
}
|
||||
|
||||
$experts = [];
|
||||
if (!empty($expertIds)) {
|
||||
$ids = array_map('intval', explode(',', $expertIds));
|
||||
$experts = Db::name('expert')->where('expert_id', 'in', $ids)->where('state', 0)->select();
|
||||
} else {
|
||||
$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;
|
||||
});
|
||||
$experts = array_values($experts);
|
||||
}
|
||||
|
||||
if (empty($experts)) {
|
||||
return jsonError('No eligible experts found (all may have been promoted recently)');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$taskId = Db::name('promotion_task')->insertGetId([
|
||||
'journal_id' => $journalId,
|
||||
'template_id' => $templateId,
|
||||
'style_id' => $styleId,
|
||||
'scene' => $scene,
|
||||
'task_name' => $taskName ?: ('Task ' . date('Y-m-d H:i')),
|
||||
'total_count' => count($experts),
|
||||
'sent_count' => 0,
|
||||
'fail_count' => 0,
|
||||
'bounce_count' => 0,
|
||||
'state' => 0,
|
||||
'smtp_ids' => $smtpIds,
|
||||
'min_interval' => max(5, $minInterval),
|
||||
'max_interval' => max($minInterval, $maxInterval),
|
||||
'max_bounce_rate' => $maxBounceRate,
|
||||
'no_repeat_days' => $noRepeatDays,
|
||||
'send_start_hour' => $sendStartHour,
|
||||
'send_end_hour' => $sendEndHour,
|
||||
'ctime' => $now,
|
||||
'utime' => $now,
|
||||
]);
|
||||
|
||||
$logs = [];
|
||||
foreach ($experts as $expert) {
|
||||
$logs[] = [
|
||||
'task_id' => $taskId,
|
||||
'expert_id' => intval($expert['expert_id']),
|
||||
'j_email_id' => 0,
|
||||
'email_to' => $expert['email'],
|
||||
'subject' => '',
|
||||
'state' => 0,
|
||||
'error_msg' => '',
|
||||
'send_time' => 0,
|
||||
'ctime' => $now,
|
||||
];
|
||||
}
|
||||
Db::name('promotion_email_log')->insertAll($logs);
|
||||
|
||||
return jsonSuccess([
|
||||
'task_id' => $taskId,
|
||||
'total_count' => count($experts),
|
||||
'state' => 0,
|
||||
'msg' => 'Task created, call startTask to begin sending',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or resume a promotion task
|
||||
*/
|
||||
public function startTask()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_id', 0));
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$task = Db::name('promotion_task')->where('task_id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return jsonError('Task not found');
|
||||
}
|
||||
if ($task['state'] == 3) {
|
||||
return jsonError('Task already completed');
|
||||
}
|
||||
if ($task['state'] == 4) {
|
||||
return jsonError('Task has been cancelled');
|
||||
}
|
||||
if ($task['state'] == 1) {
|
||||
return jsonError('Task is already running');
|
||||
}
|
||||
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update([
|
||||
'state' => 1,
|
||||
'utime' => time(),
|
||||
]);
|
||||
|
||||
(new PromotionService())->enqueueNextEmail($taskId, 0);
|
||||
|
||||
return jsonSuccess([
|
||||
'task_id' => $taskId,
|
||||
'state' => 1,
|
||||
'msg' => 'Task started, emails will be sent via queue',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause a running task
|
||||
*/
|
||||
public function pauseTask()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_id', 0));
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$task = Db::name('promotion_task')->where('task_id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return jsonError('Task not found');
|
||||
}
|
||||
if ($task['state'] != 1) {
|
||||
return jsonError('Can only pause a running task (current state: ' . $task['state'] . ')');
|
||||
}
|
||||
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update([
|
||||
'state' => 2,
|
||||
'utime' => time(),
|
||||
]);
|
||||
|
||||
return jsonSuccess(['task_id' => $taskId, 'state' => 2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a task (cannot be resumed)
|
||||
*/
|
||||
public function cancelTask()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_id', 0));
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$task = Db::name('promotion_task')->where('task_id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return jsonError('Task not found');
|
||||
}
|
||||
if ($task['state'] == 3 || $task['state'] == 4) {
|
||||
return jsonError('Task already finished/cancelled');
|
||||
}
|
||||
|
||||
Db::name('promotion_task')->where('task_id', $taskId)->update([
|
||||
'state' => 4,
|
||||
'utime' => time(),
|
||||
]);
|
||||
|
||||
Db::name('promotion_email_log')
|
||||
->where('task_id', $taskId)
|
||||
->where('state', 0)
|
||||
->update(['state' => 4, 'error_msg' => 'Task cancelled']);
|
||||
|
||||
return jsonSuccess(['task_id' => $taskId, 'state' => 4]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list for a journal
|
||||
*/
|
||||
public function getTaskList()
|
||||
{
|
||||
$journalId = intval($this->request->param('journal_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));
|
||||
|
||||
$where = [];
|
||||
if ($journalId) {
|
||||
$where['journal_id'] = $journalId;
|
||||
}
|
||||
if ($state !== '-1' && $state !== '') {
|
||||
$where['state'] = intval($state);
|
||||
}
|
||||
|
||||
$total = Db::name('promotion_task')->where($where)->count();
|
||||
$list = Db::name('promotion_task')
|
||||
->where($where)
|
||||
->order('task_id desc')
|
||||
->page($page, $perPage)
|
||||
->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$pending = Db::name('promotion_email_log')
|
||||
->where('task_id', $item['task_id'])
|
||||
->where('state', 0)
|
||||
->count();
|
||||
$item['pending_count'] = $pending;
|
||||
$item['progress'] = $item['total_count'] > 0
|
||||
? round(($item['sent_count'] + $item['fail_count']) / $item['total_count'] * 100, 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
return jsonSuccess([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => $total > 0 ? ceil($total / $perPage) : 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task detail with stats
|
||||
*/
|
||||
public function getTaskDetail()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_id', 0));
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$task = Db::name('promotion_task')->where('task_id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return jsonError('Task not found');
|
||||
}
|
||||
|
||||
$stats = Db::name('promotion_email_log')
|
||||
->field('state, count(*) as cnt')
|
||||
->where('task_id', $taskId)
|
||||
->group('state')
|
||||
->select();
|
||||
|
||||
$stateMap = ['pending' => 0, 'sent' => 0, 'failed' => 0, 'bounce' => 0, 'cancelled' => 0];
|
||||
foreach ($stats as $s) {
|
||||
switch ($s['state']) {
|
||||
case 0: $stateMap['pending'] = $s['cnt']; break;
|
||||
case 1: $stateMap['sent'] = $s['cnt']; break;
|
||||
case 2: $stateMap['failed'] = $s['cnt']; break;
|
||||
case 3: $stateMap['bounce'] = $s['cnt']; break;
|
||||
case 4: $stateMap['cancelled'] = $s['cnt']; break;
|
||||
}
|
||||
}
|
||||
|
||||
$task['log_stats'] = $stateMap;
|
||||
$task['progress'] = $task['total_count'] > 0
|
||||
? round(($task['sent_count'] + $task['fail_count']) / $task['total_count'] * 100, 1)
|
||||
: 0;
|
||||
$task['bounce_rate'] = ($task['sent_count'] > 0)
|
||||
? round($task['bounce_count'] / $task['sent_count'] * 100, 1)
|
||||
: 0;
|
||||
|
||||
return jsonSuccess($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sending logs for a task
|
||||
*/
|
||||
public function getTaskLogs()
|
||||
{
|
||||
$taskId = intval($this->request->param('task_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', 50)), 200));
|
||||
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
}
|
||||
|
||||
$where = ['l.task_id' => $taskId];
|
||||
if ($state !== '-1' && $state !== '') {
|
||||
$where['l.state'] = intval($state);
|
||||
}
|
||||
|
||||
$total = Db::name('promotion_email_log')->alias('l')->where($where)->count();
|
||||
$list = Db::name('promotion_email_log')->alias('l')
|
||||
->join('t_expert e', 'l.expert_id = e.expert_id', 'LEFT')
|
||||
->where($where)
|
||||
->field('l.*, e.name as expert_name, e.affiliation')
|
||||
->order('l.log_id asc')
|
||||
->page($page, $perPage)
|
||||
->select();
|
||||
|
||||
return jsonSuccess([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total_pages' => $total > 0 ? ceil($total / $perPage) : 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron: check bounce emails and update promotion logs accordingly
|
||||
* Should be called periodically after syncInbox runs.
|
||||
*/
|
||||
public function syncBounceToLogs()
|
||||
{
|
||||
$journalId = intval($this->request->param('journal_id', 0));
|
||||
|
||||
$where = ['is_bounce' => 1, 'state' => 0];
|
||||
if ($journalId) {
|
||||
$where['journal_id'] = $journalId;
|
||||
}
|
||||
|
||||
$bounces = Db::name('email_inbox')
|
||||
->where($where)
|
||||
->where('bounce_email', '<>', '')
|
||||
->select();
|
||||
|
||||
$updated = 0;
|
||||
foreach ($bounces as $bounce) {
|
||||
$affected = Db::name('promotion_email_log')
|
||||
->where('email_to', strtolower($bounce['bounce_email']))
|
||||
->where('state', 1)
|
||||
->update(['state' => 3]);
|
||||
|
||||
if ($affected > 0) {
|
||||
$updated += $affected;
|
||||
|
||||
$taskIds = Db::name('promotion_email_log')
|
||||
->where('email_to', strtolower($bounce['bounce_email']))
|
||||
->where('state', 3)
|
||||
->column('task_id');
|
||||
|
||||
$taskIds = array_unique($taskIds);
|
||||
foreach ($taskIds as $tid) {
|
||||
$bounceCount = Db::name('promotion_email_log')
|
||||
->where('task_id', $tid)
|
||||
->where('state', 3)
|
||||
->count();
|
||||
Db::name('promotion_task')
|
||||
->where('task_id', $tid)
|
||||
->update(['bounce_count' => $bounceCount, 'utime' => time()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jsonSuccess(['bounce_logs_updated' => $updated]);
|
||||
}
|
||||
|
||||
// ==================== Internal Methods ====================
|
||||
|
||||
/**
|
||||
@@ -1079,7 +1525,7 @@ class EmailClient extends Base
|
||||
private function doSendEmail($account, $toEmail, $subject, $htmlContent)
|
||||
{
|
||||
try {
|
||||
$mail = new PHPMailer\PHPMailer(true);
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->SMTPDebug = 0;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
@@ -1130,4 +1576,69 @@ class EmailClient extends Base
|
||||
|
||||
return str_replace(array_keys($vars), array_values($vars), $template);
|
||||
}
|
||||
|
||||
private function buildExpertVars($expert)
|
||||
{
|
||||
return [
|
||||
'name' => $expert['name'] ?? '',
|
||||
'email' => $expert['email'] ?? '',
|
||||
'affiliation' => $expert['affiliation'] ?? '',
|
||||
'field' => $expert['field'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
private function buildJournalVars($journal)
|
||||
{
|
||||
if (!$journal) return [];
|
||||
return [
|
||||
'journal_title' => $journal['title'] ?? '',
|
||||
'journal_abbr' => $journal['jabbr'] ?? '',
|
||||
'journal_url' => $journal['website'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
private function renderVars($tpl, $vars)
|
||||
{
|
||||
if (!is_string($tpl) || $tpl === '') return '';
|
||||
if (!is_array($vars) || empty($vars)) return $tpl;
|
||||
|
||||
$replace = [];
|
||||
foreach ($vars as $k => $v) {
|
||||
$key = trim((string)$k);
|
||||
if ($key === '') continue;
|
||||
$replace['{{' . $key . '}}'] = (string)$v;
|
||||
$replace['{' . $key . '}'] = (string)$v;
|
||||
}
|
||||
return str_replace(array_keys($replace), array_values($replace), $tpl);
|
||||
}
|
||||
|
||||
private function renderFromTemplate($templateId, $journalId, $varsJson, $styleId = 0)
|
||||
{
|
||||
$tpl = Db::name('mail_template')->where('template_id', $templateId)->where('journal_id', $journalId)->where('state', 0)->find();
|
||||
if (!$tpl) {
|
||||
return ['code' => 1, 'msg' => 'Template not found'];
|
||||
}
|
||||
|
||||
$vars = [];
|
||||
if ($varsJson) {
|
||||
$decoded = json_decode($varsJson, true);
|
||||
if (is_array($decoded)) $vars = $decoded;
|
||||
}
|
||||
|
||||
$subject = $this->renderVars($tpl['subject'], $vars);
|
||||
$body = $this->renderVars($tpl['body_html'], $vars);
|
||||
$finalBody = $body;
|
||||
|
||||
// 新版 style 表:只使用 header_html / footer_html 作为整体风格包裹
|
||||
if ($styleId) {
|
||||
$style = Db::name('mail_style')->where('style_id', $styleId)->where('state', 0)->find();
|
||||
if ($style) {
|
||||
$header = $style['header_html'] ?? '';
|
||||
$footer = $style['footer_html'] ?? '';
|
||||
$finalBody = $header . $body . $footer;
|
||||
}
|
||||
}
|
||||
|
||||
return ['code' => 0, 'msg' => 'success', 'data' => ['subject' => $subject, 'body' => $finalBody]];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user