'noindex,nofollow']); ?>
请输入管理员用户名与口令(当前口令仍是 config.php 里的明文,登录后请到「站点设置」改成加密存储)。
这是一封来自 ' . htmlspecialchars($site, ENT_QUOTES, 'UTF-8') . ' 的测试邮件。
' . '如果你收到了它,说明 SMTP 配置正确,回复通知可以正常发出。
' . '发送时间:' . date('Y-m-d H:i:s') . '
'; list($ok, $err) = fb_smtp_send($sc, $to, '【' . $site . '】邮件测试', $body); if ($ok) { $msg = '测试邮件已发出,请查收 ' . $to . '(也看一下垃圾箱)。'; $msgType = 'ok'; } else { $msg = '发送失败:' . $err; $msgType = 'error'; } } } elseif (fb_admin_is_taxonomy_act($act)) { list($msg, $msgType) = fb_admin_taxonomy_action($pdo, $act, $_POST); } elseif ($act === 'settings') { $msg = fb_admin_save_settings(); } elseif ($act === 'password') { $old = (string)(isset($_POST['old_password']) ? $_POST['old_password'] : ''); $n1 = (string)(isset($_POST['new_password']) ? $_POST['new_password'] : ''); $n2 = (string)(isset($_POST['new_password2']) ? $_POST['new_password2'] : ''); // 从没设过口令(或仍是出厂占位值)时,跳过"当前口令"校验 —— 否则谁也进不来、又改不了 $firstSet = !fb_admin_password_is_set(); if (!$firstSet && !fb_admin_verify_password($old)) { $msg = '当前口令不正确。'; $msgType = 'error'; } elseif (fb_len($n1) < 8) { $msg = '新口令至少 8 个字符。'; $msgType = 'error'; } elseif ($n1 !== $n2) { $msg = '两次输入的新口令不一致。'; $msgType = 'error'; } else { fb_admin_set_password($n1); fb_mod_log('password', null, $firstSet ? '首次设定登录口令(bcrypt 哈希存库)' : '修改登录口令'); $msg = $firstSet ? '口令已设定,下次登录请使用它。' : '口令已修改,下次登录请使用新口令。'; } } } } /** * 保存站点设置(把表单值写进 settings 表;config.php 仍是出厂默认值) * @return string 提示语 */ function fb_admin_save_settings() { // PHP 会把表单字段名里的「.」替换成「_」,所以两种写法都读一遍 $post = function ($k, $def = null) { if (isset($_POST[$k])) { return $_POST[$k]; } $alt = str_replace('.', '_', $k); if (isset($_POST[$alt])) { return $_POST[$alt]; } return $def; }; // 站点信息(对外展示,用于说明「本系统为哪个软件提供反馈服务」) // 留空 = 删除该设置项,回落到 config.php 的出厂值,便于恢复默认。 foreach (['site_name', 'site_desc'] as $k) { $v = trim((string)$post($k, '')); if ($v === '') { fb_unset_setting($k); } else { fb_set_setting($k, $v); } } // 页脚:是否显示「由 FeedLite 驱动」这句版权,以及链接指向的开源仓库地址 fb_set_setting('footer.show_credit', $post('footer_show_credit') ? 1 : 0); $curl = trim((string)$post('footer_credit_url', '')); // 只接受 http/https:这条会被直接渲染成 ,不能让 javascript:/data: 混进来 if ($curl !== '' && !preg_match('~^https?://~i', $curl)) { return '页脚链接地址需以 http:// 或 https:// 开头。'; } if ($curl === '' || $curl === fb_credit_url_default()) { fb_unset_setting('footer.credit_url'); } else { fb_set_setting('footer.credit_url', $curl); } $ints = [ 'upload.image_max_per_post' => [0, 20], 'upload.video_max_per_post' => [0, 10], 'upload.retention_days' => [1, 3650], 'upload.upload_per_hour' => [1, 2000], 'upload.image_max_side' => [480, 4096], ]; foreach ($ints as $k => $range) { $v = (int)$post($k, 0); $v = max($range[0], min($range[1], $v)); fb_set_setting($k, $v); } // MB → 字节 $mbs = [ 'upload.image_max_bytes' => [0.2, 50], 'upload.video_max_bytes' => [1, 500], 'upload.total_cap_bytes' => [10, 100000], ]; foreach ($mbs as $k => $range) { $mb = (float)$post($k, 0); $mb = max($range[0], min($range[1], $mb)); fb_set_setting($k, (int)round($mb * 1048576)); } $storage = (string)$post('attachment.storage', 'off'); if (!in_array($storage, ['off', 'local', 'oss'], true)) { $storage = 'off'; } fb_set_setting('attachment.storage', $storage); // OSS 模式下 oss.enabled 必须跟着开;其它模式关掉,避免误判 fb_set_setting('oss.enabled', $storage === 'oss' ? 1 : 0); $texts = ['oss.endpoint', 'oss.bucket', 'oss.instance_role', 'oss.domain', 'oss.prefix', 'moderation.close_mail_subject']; foreach ($texts as $k) { $v = trim((string)$post($k, '')); // 对象键前缀留空会让对象键变成 "/2026/...",回落到出厂默认 'fb' if ($k === 'oss.prefix' && $v === '') { fb_unset_setting($k); continue; } // RAM 角色留空 = 让程序自己去元数据服务探测,不写死一个空值 if ($k === 'oss.instance_role' && $v === '') { list($rOk, $rVal) = fb_oss_detect_instance_role(); if ($rOk) { fb_set_setting($k, (string)$rVal); } continue; // 探测不到就保持原值不动 } fb_set_setting($k, $v); } $src = (string)$post('oss.credential_source', 'instance_role'); fb_set_setting('oss.credential_source', $src === 'static' ? 'static' : 'instance_role'); fb_set_setting('oss.access_key', trim((string)$post('oss.access_key', ''))); $sk = trim((string)$post('oss.secret_key', '')); if ($sk !== '') { fb_set_setting('oss.secret_key', $sk); } fb_set_setting('oss.visibility', ((string)$post('oss.visibility', 'public')) === 'private' ? 'private' : 'public'); fb_set_setting('oss.url_scheme', ((string)$post('oss.url_scheme', 'https')) === 'http' ? 'http' : 'https'); fb_set_setting('oss.verify', $post('oss.verify') ? 1 : 0); fb_set_setting('oss.image_process', $post('oss.image_process') ? 1 : 0); fb_set_setting('oss.use_internal_endpoint', $post('oss.use_internal_endpoint') ? 1 : 0); /* ---------------- 邮件通知(SMTP) ---------------- */ $smtpText = ['smtp.host', 'smtp.user', 'smtp.from', 'smtp.from_name']; foreach ($smtpText as $k) { fb_set_setting($k, trim((string)$post($k, ''))); } // 口令留空 = 不修改(避免每次保存都得重填) $sp = (string)(isset($_POST['smtp_pass']) ? $_POST['smtp_pass'] : ''); if (trim($sp) !== '') { fb_set_setting('smtp.pass', $sp); } $smtpPort = (int)$post('smtp.port', 465); if ($smtpPort <= 0 || $smtpPort > 65535) { $smtpPort = 465; } fb_set_setting('smtp.port', $smtpPort); $sec = strtolower(trim((string)$post('smtp.secure', 'ssl'))); if (!in_array($sec, ['ssl', 'tls', ''], true)) { $sec = 'ssl'; } fb_set_setting('smtp.secure', $sec); $smtpTo = (int)$post('smtp.timeout', 15); fb_set_setting('smtp.timeout', max(5, min(60, $smtpTo))); fb_set_setting('smtp.verify_ssl', (int)(bool)$post('smtp.verify_ssl')); // 没填 from 时用 user 兜底(绝大多数 SMTP 要求两者一致) if (trim((string)fb_cfg('smtp.from', '')) === '') { fb_set_setting('smtp.from', trim((string)fb_cfg('smtp.user', ''))); } // 只有 host 填了才允许开启,否则开着一个空配置只会每次发信都失败 $wantOn = (bool)$post('smtp.enabled'); $hostNow = trim((string)fb_cfg('smtp.host', '')); fb_set_setting('smtp.enabled', ($wantOn && $hostNow !== '') ? 1 : 0); if ($wantOn && $hostNow === '') { $msg = '邮件通知未开启:请先填写 SMTP 服务器地址。'; $msgType = 'error'; } /* ---------------- 通知开关 ---------------- */ fb_set_setting('notify.enabled', (int)(bool)$post('notify.enabled')); fb_set_setting('notify.on_every_reply', (int)(bool)$post('notify.on_every_reply')); fb_set_setting('notify.admin_new_thread', (int)(bool)$post('notify.admin_new_thread')); $cd = (int)$post('notify.cooldown_seconds', 60); fb_set_setting('notify.cooldown_seconds', max(0, min(86400, $cd))); fb_set_setting('moderation.enabled', $post('moderation.enabled') ? 1 : 0); fb_mod_log('settings', null, '保存站点设置:存储=' . $storage); return '设置已保存。'; } /* ================================================================== * 页面 * ================================================================== */ $tab = (string)(isset($_GET['tab']) ? $_GET['tab'] : 'pending'); if (!in_array($tab, ['pending', 'all', 'thread', 'taxonomy', 'statuses', 'settings', 'log'], true)) { $tab = 'pending'; } fb_layout_head('管理后台', ['robots' => 'noindex,nofollow']); ?>登录状态 小时内有效,超时自动退出。
当前没有待审核的反馈。
没有符合条件的问题。
问题不存在。返回列表
—
鉴权方式:
凭证状态:
Bucket:
Endpoint(配置):
Endpoint(实际连接): —
连通性:
↳ 内网 Endpoint 跨地域不可达:请取消勾选上方「使用内网 Endpoint」后保存,或把桶换到与本机同地域。
测一下桶连通性
(会真实访问一次 OSS,跨地域不通时可能等待数秒)
已用附件: / 上限
settings 表(键名 admin.password_hash)
config.php 的 admin_password
尚未设置
是否加密:
是 —— 存的是 password_hash() 生成的 bcrypt 单向哈希($2y$…),
不可逆、无法还原明文;校验用 password_verify()。
否 —— 目前是明文写在 config.php 里。
在下面改一次口令后即会转存为数据库中的 bcrypt 哈希。
—— 还没有可用口令,当前后台无法登录。请用下方表单设定一个。
数据库文件在 data/feedback.db(已由 .htaccess 拒绝外部访问),且只存哈希,明文不落盘。
还没有操作记录。