Files
freeshop/cron_expire_notify.php
2026-08-14 10:55:06 +08:00

119 lines
4.6 KiB
PHP

<?php
if (!defined('IN_APP')) {
define('IN_APP', true);
}
require __DIR__ . '/config.php';
require __DIR__ . '/includes/db.php';
require __DIR__ . '/includes/functions.php';
$isCli = (PHP_SAPI === 'cli');
$dryRun = false;
$args = $isCli ? array_slice($argv, 1) : [];
if (in_array('--dry-run', $args, true)) {
$dryRun = true;
}
// Web 触发需携带正确的 key,否则拒绝
if (!$isCli) {
header('Content-Type: text/plain; charset=utf-8');
$validKey = getSetting('cron_notify_key', '');
if ($validKey === '') {
$validKey = defined('SITE_KEY') ? SITE_KEY : '';
}
$got = (string) ($_GET['key'] ?? '');
if ($validKey === '' || !hash_equals($validKey, $got)) {
http_response_code(403);
echo "Access denied\n";
exit;
}
if (isset($_GET['dry'])) {
$dryRun = true;
}
}
$notifyDays = 5;
$lo = date('Y-m-d H:i:s', strtotime('+' . ($notifyDays - 1) . ' days'));
$hi = date('Y-m-d H:i:s', strtotime('+' . ($notifyDays + 1) . ' days'));
$sql = 'SELECT o.id, o.order_no, o.expires_at, o.contact_email, u.email AS u_email, u.username, u.nickname
FROM ' . tn('orders') . ' o
LEFT JOIN ' . tn('users') . ' u ON u.id = o.user_id
WHERE o.expires_at IS NOT NULL
AND o.expires_at > NOW()
AND o.expires_at >= ? AND o.expires_at <= ?
AND o.expire_notify_sent = 0
AND o.status IN (\'paid\', \'shipped\', \'completed\')';
$stmt = db()->prepare($sql);
$stmt->execute([$lo, $hi]);
$rows = $stmt->fetchAll();
$sent = 0;
$skipped = 0;
$errors = [];
$preview = [];
foreach ($rows as $row) {
$to = !empty($row['contact_email']) ? $row['contact_email'] : $row['u_email'];
if (empty($to)) {
$skipped++;
$errors[] = '订单 #' . $row['id'] . ' 无收件邮箱,已跳过';
continue;
}
$daysLeft = (int) ceil((strtotime($row['expires_at']) - time()) / 86400);
$name = $row['nickname'] ?: $row['username'];
$subject = '[' . SITE_NAME . '] 您的服务即将到期(剩余 ' . $daysLeft . ' 天),请及时续期';
$body = buildExpireMail($name, $row['order_no'], $row['expires_at'], $daysLeft);
if ($dryRun) {
$preview[] = '预览 #' . $row['id'] . ' → ' . $to . ' | 剩余 ' . $daysLeft . ' 天';
$sent++;
continue;
}
$res = fnwSendMail($to, $subject, $body);
if (!empty($res['ok'])) {
db()->prepare('UPDATE ' . tn('orders') . ' SET expire_notify_sent = 1 WHERE id = ?')->execute([$row['id']]);
$sent++;
} else {
$errors[] = '订单 #' . $row['id'] . ' 发送失败:' . ($res['error'] ?? '未知错误');
}
}
$out = [];
$out[] = '[' . date('Y-m-d H:i:s') . '] 到期前提醒任务' . ($dryRun ? '(预览模式)' : '');
$out[] = '扫描窗口:' . $lo . ' ~ ' . $hi . '(距到期约 ' . ($notifyDays - 1) . '~' . ($notifyDays + 1) . ' 天)';
$out[] = '命中订单:' . count($rows) . ' 笔;已发送:' . $sent . ';跳过:' . $skipped;
if ($dryRun && $preview) {
$out[] = '--- 预览列表 ---';
$out = array_merge($out, $preview);
}
if ($errors) {
$out[] = '--- 注意事项 ---';
$out = array_merge($out, $errors);
}
$out[] = '';
$text = implode("\n", $out);
if ($isCli) {
fwrite(STDOUT, $text);
} else {
echo $text;
}
/**
* 生成到期提醒邮件正文(HTML)。
*/
function buildExpireMail($name, $orderNo, $expiresAt, $daysLeft)
{
$exp = date('Y-m-d H:i', strtotime($expiresAt));
$site = SITE_NAME;
return '<div style="font-family:Segoe UI,Helvetica,Arial,sans-serif;max-width:560px;margin:0 auto;padding:24px;'
. 'border:1px solid #e3e8ef;border-radius:16px;">'
. '<h2 style="color:#1a73e8;margin:0 0 12px;">服务即将到期提醒</h2>'
. '<p style="color:#1f2430;line-height:1.7;">你好 ' . h($name) . ',你在 ' . h($site) . ' 的订单 <strong>' . h($orderNo) . '</strong> 所对应的服务即将到期。</p>'
. '<div style="background:#f5f7fb;border-left:4px solid #f59e0b;padding:12px 16px;margin:12px 0;color:#1f2430;">'
. '<p style="margin:4px 0;">到期时间:<strong>' . h($exp) . '</strong></p>'
. '<p style="margin:4px 0;">剩余天数:<strong style="color:#b7791f;">约 ' . (int) $daysLeft . ' 天</strong></p>'
. '</div>'
. '<p style="color:#1f2430;line-height:1.7;">为避免服务中断,请在到期前通过「我的订单 → 对应订单 → 申请续期」提交工单,或联系客服办理续期。</p>'
. '<p style="color:#5f6b7a;font-size:.85rem;margin-top:16px;">若已办理续期请忽略此邮件。本邮件由系统自动发送,请勿直接回复。</p>'
. '</div>';
}