119 lines
4.6 KiB
PHP
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>';
|
|
}
|