Files
FeedLite/cron.php
T
laoyang cede838b5e FeedLite 初始提交:通用轻量级反馈系统(PHP + SQLite,零依赖)
- 前台:问题列表 / 详情 / 发布与回复(趣味问答验证码、蜜罐、IP 限流)
- 后台:先审后显、站点设置(站点标题/描述、附件存储、OSS 参数)、操作日志
- 附件:关闭 / 本机硬盘 / 阿里云 OSS 浏览器直传(ECS 实例 RAM 角色)三种模式
- 品牌资源:全套图标(favicon.ico / SVG / PNG 各尺寸 / manifest)
- 安全:CSRF、session 加固、.htaccess 屏蔽敏感文件、口令仅存哈希
- 配置:config.example.php 为模板,config.php 不入库
2026-09-28 20:34:37 +08:00

93 lines
3.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 维护脚本(建议每天跑一次)
* ------------------------------------------------------------------
* 1) 清理超过保留期的附件(先删 OSS 对象,再删库记录)
* 2) 补发失败的通知邮件
*
* 用法(宝塔 → 计划任务 → Shell 脚本):
* php /www/wwwroot/xxx/feedback/cron.php --token=你的api_token
* 命令行(CLI)运行时可不带令牌。
*/
require __DIR__ . '/lib/bootstrap.php';
$isCli = (PHP_SAPI === 'cli');
if (!$isCli) {
header('Content-Type: text/plain; charset=utf-8');
$token = isset($_GET['token']) ? (string)$_GET['token'] : '';
$expected = (string)fb_cfg('api_token');
if ($expected === '' || !hash_equals($expected, $token)) {
http_response_code(403);
echo "forbidden\n";
exit;
}
}
$pdo = fb_db();
/* ---------------- 1. 附件过期清理 ---------------- */
$days = (int)fb_upload_cfg('retention_days', 365);
$kept = (int)$pdo->query('SELECT COUNT(*) FROM attachments')->fetchColumn();
$gone = 0;
if ($days > 0) {
$gone = fb_attach_cleanup($pdo, $days, 500);
}
echo "附件:共 {$kept} 个,已清理超过 {$days} 天的附件 {$gone} 个(剩余 "
. (int)$pdo->query('SELECT COUNT(*) FROM attachments')->fetchColumn() . " 个,占用 "
. fb_size_h(fb_attach_total_bytes($pdo)) . ")。\n";
/* ---------------- 1b. 附件存储凭证自检 ----------------
* 顺手确认一次「能不能从 ECS 实例 RAM 角色拿到上传凭证」:角色名写错、
* 角色没授予实例、元数据服务被网络策略挡住时,这里会直接说明原因,
* 比等用户上传失败再来排查容易得多。
*/
if (fb_oss_enabled()) {
$cs = fb_oss_cred_status();
$line = '附件存储凭证:' . $cs['label'] . '(AK 尾号 ' . ($cs['ak_tail'] !== '' ? '…' . $cs['ak_tail'] : '无') . ')';
if ($cs['has_token']) { $line .= ',已取得临时安全令牌'; }
if ($cs['expire'] > 0) { $line .= ',有效期至 ' . date('Y-m-d H:i', $cs['expire']); }
if (!$cs['ok']) { $line .= ' ⚠️ 取不到凭证:' . ($cs['error'] !== '' ? $cs['error'] : '未知原因'); }
echo $line . "。\n";
} else {
echo "附件存储未启用(请在 config.php 的 oss 段填写 endpoint / bucket / instance_role 并置 enabled = true)。\n";
}
/* ---------------- 2. 通知邮件补发 ---------------- */
$smtp = (array)fb_cfg('smtp');
if (empty($smtp['enabled'])) {
echo "SMTP 未启用,跳过邮件补发。\n";
exit;
}
$limit = 50;
$rows = $pdo->query(
'SELECT * FROM notify_log WHERE ok = 0 AND tries < 5 ORDER BY id ASC LIMIT ' . $limit
)->fetchAll();
$sent = 0; $fail = 0;
foreach ($rows as $n) {
$st = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
$st->execute([(int)$n['thread_id']]);
$thread = $st->fetch();
if (!$thread) { continue; }
$st = $pdo->prepare('SELECT * FROM replies WHERE id = ?');
$st->execute([(int)$n['reply_id']]);
$reply = $st->fetch();
if (!$reply) { continue; }
$isAdmin = !empty($reply['is_admin']);
$subject = '【' . fb_cfg('site_name') . '】有人回复了你的问题:' . fb_excerpt($thread['title'], 40);
$html = fb_notify_html($thread, $reply, $isAdmin);
list($ok, $err) = fb_smtp_send($smtp, (string)$n['to_email'], $subject, $html);
$up = $pdo->prepare('UPDATE notify_log SET ok = ?, tries = tries + 1, err = ? WHERE id = ?');
$up->execute([$ok ? 1 : 0, $ok ? null : $err, (int)$n['id']]);
if ($ok) { $sent++; } else { $fail++; }
}
echo "邮件:成功 {$sent} 封,失败 {$fail} 封(最多处理 {$limit} 条待发记录)。\n";