Files
FeedLite/config.example.php
T
laoyang 55d2dfa46b 后台管理界面:软件/分类/状态可配,反馈可改挂归属 (#1)
## 新增 lib/admin_taxonomy.php
- 软件:增删改、排序、启停;新增时自动预置 Bug/功能建议/其他 三个分类
- 分类:在选定软件下增删改、排序、启停
- 状态:改名、改「给用户看的话」、排序、启停;标识不可改(历史数据靠它对应)
- 反馈改挂:th_move,可把反馈移到别的软件/分类,跨软件的非法分类会被拒

## 安全与边界
- 所有动作走 CSRF 校验(沿用现有机制)
- 不能删最后一个软件 / 没有分类的软件至少要留一个启用分类
- 有反馈挂着的软件或分类不能删,提示改为停用(停用后前台消失、数据保留)
- th_move 加入 fb_admin_is_taxonomy_act 白名单,否则会被「问题不存在」静默吞掉

## admin.php 接线
- 引入模块 + 动作分发 + tab 白名单加 taxonomy/statuses + 导航两项
- 前置检查放行不依赖 $t 的动作(否则新增软件报「问题不存在」)
- 全部问题列表:加软件筛选、分类跟着软件走;修掉三处仍在用旧
  fb_statuses()/fb_categories() 的遍历(那会渲染出错)
- 详情页加「归属」表单,软件徽章可显示

## 其他文件
- api.php:状态/分类改数据库;新增 ?sw= / ?software_id= / ?cid= 过滤,
  老的 ?category=<slug> 仍可用(在所属软件内解析)
- thread.php:详情页显示软件与分类徽章
- config.example.php:移除 categories / statuses,改由数据库管理

## 修掉两个真实 bug
1. 移除配置后出厂分类兜底只剩 4 个,漏了「同步问题」—— 建库后核对数量才发现
2. th_move 不在白名单,POST 后返回 200 却静默不生效

验证:后台 e2e 29/29;改挂与联动 13/13;PHP 8.5 全部入口 0 弃用;
19 个 PHP 文件语法全过。
2026-11-01 12:03:11 +08:00

185 lines
12 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
/**
* FeedLite · 轻反馈 · 配置文件
* ------------------------------------------------------------------
* 部署后请务必修改 admin_password 与 api_token(及 ip_salt),
* 并按需填写下方的 smtp 段以开启"回复邮件通知"。
*/
return [
/* ---------------- 基本 ---------------- */
// SQLite 数据库文件路径。
// 放在站内 data/ 目录(已被 .htaccess 拒绝直接访问);也可改为站点目录之外的绝对路径。
'db_path' => __DIR__ . '/data/feedback.db',
'site_name' => '轻反馈',
'site_desc' => '欢迎在这里反馈使用问题、提出功能建议。任何人都可以提问与回复,无需注册;只需填写昵称与邮箱。',
// 展示用的官方邮箱(可留空)
'admin_email' => 'support@example.com',
// 站点根地址(用于邮件正文里的链接)。留空则按访问域名自动探测。
// 例如:'https://feedback.example.com'
'base_url' => '',
/* ---------------- 页脚 ----------------
* 页脚永远会显示「管理后台」入口;这里的开关只控制右侧那句版权说明。
* show_credit = false:页脚不再出现「由 FeedLite 驱动」,只留管理后台链接。
*/
'footer' => [
'show_credit' => true,
'credit_url' => 'https://code.yangyq.net/laoyang/FeedLite', // 页脚「FeedLite」二字指向的地址
],
/* ---------------- 安全 ----------------
* 管理员口令的存放规则(重要):
* · 正常安装(走 install.php,或登录后台改口令)→ 口令以 **bcrypt 单向哈希**
* 存在数据库 settings 表的 admin.password_hash,**明文不落盘**。
* · 下面这个 admin_password 只是「初始值」通道,且**出厂占位值已被程序明确禁用**:
* 值仍为 CHANGE_ME_admin 时,任何人(包括你)都无法用它登录后台。
* · 想手工指定初始口令,就把下面这行改成你自己的口令;登录一次后建议立即改掉,
* 改完即转为数据库哈希存储、这里可以再改回占位值。
*/
// ★★ 留空或保持占位值 = 未设置口令(后台不可登录,需先跑安装向导)★★
'admin_password' => 'CHANGE_ME_admin',
// API 令牌:供自动化脚本 / AI 读取数据用。★ 请务必修改 ★
'api_token' => 'CHANGE_ME_api_token',
// 对访客 IP 做加盐哈希(数据库只保存哈希,不保存明文 IP)。★ 请修改为随机值 ★
'ip_salt' => 'CHANGE_ME_random_salt',
/* ---------------- 问题分类与处理状态:已移入数据库 ----------------
* 不再写在这里。安装时会把出厂值写进 data/ 的数据库,
* 之后在「管理后台 → 软件与分类 / 处理状态」里增删改,改这里无效。
*
* 出厂分类(首个软件下):使用问题 / Bug 反馈 / 功能建议 / 同步问题 / 其他
* 出厂状态(5 个):待处理 / 处理中 / 已解决 / 已关闭 / 不予考虑
* 每个状态都带一句「给用户看的话」,会在通知邮件里发给提交人。
*/
/* ---------------- 邮件通知(SMTP) ---------------- */
'smtp' => [
'enabled' => false, // 填好下面参数后改为 true 才会真正发信
'host' => 'smtp.example.com', // 如 smtp.qiye.aliyun.com / smtp.exmail.qq.com
'port' => 465,
'secure' => 'ssl', // ssl(465)| tls(587 STARTTLS)| ''(25 不加密)
'user' => 'noreply@example.com',
'pass' => '', // 邮箱密码或"授权码"
'from' => 'noreply@example.com', // 发件地址(通常与 user 相同)
'from_name' => '轻反馈',
'timeout' => 15,
'verify_ssl' => true, // 若服务器缺少 CA 证书可临时设为 false
],
// 通知策略
'notify' => [
'enabled' => true, // 回复时是否尝试发送通知邮件
'on_every_reply' => true, // true=任何人回复都通知发帖人;false=仅"官方"回复才通知
'cooldown_seconds' => 300, // 同一帖同一收件人的通知节流(秒)
],
/* ---------------- 审核(先审后显) ----------------
* enabled = true:新提交的问题默认**不公开**,由管理员在 admin.php 审核;
* · 通过 → 公开显示(visible=1)
* · 关闭 → 判定"不是应用的问题",保持不公开,并把管理员填写的说明邮件发给提问人
* 审核后台入口:/feedback/admin.php(口令= admin_password)
*/
'moderation' => [
'enabled' => true,
'close_mail_subject' => '关于你反馈的问题《%s》', // %s 会替换成问题标题
],
/* ---------------- 附件上传(阿里云 OSS · 服务器代理) ----------------
* ★ 架构:私有桶 + 内网 Endpoint,读写全部经服务器代理
* 桶设为【私有】、且只用【内网 Endpoint】(*-internal.aliyuncs.com,仅同地域 ECS 可达),
* 浏览器既不直传、也不直连。数据流:
* 上传:浏览器 → upload.php → 服务器 PUT 到内网 OSS
* 读取:浏览器 → media.php → 服务器 GET 内网 OSS 并流式转发(支持 Range)
* 好处:桶可彻底私有、公网完全看不到 Endpoint、AccessKey 永不出服务器。
* 代价:附件流量经过服务器(占带宽),需保证服务器磁盘临时空间够放单个上传文件。
*
* ★ 凭证方式:ECS 实例 RAM 角色(推荐,服务器上不保存长期 AccessKey)或静态 AccessKey。
* 实例角色下,程序从内网元数据服务(http://100.100.100.200,链路本地、不出公网)
* 读取一份「有时效、会自动轮换」的临时凭证来签名。AccessKey 永不落盘、永不进本文件。
*
* ★ 开通步骤(详见 README.md):
* ① 创建【与 ECS 同地域】的 Bucket,读写权限设为「私有」
* ⚠️ 内网 Endpoint 跨地域不通!ECS 在哪个地域,桶就建在哪个地域。
* ② RAM 建权限策略:只授予该 Bucket(某前缀下)的 oss:PutObject / GetObject / DeleteObject
* ③ RAM 建角色(如 FeedLiteOssRole)并赋予上一步的策略
* ④ ECS 控制台把该角色【授予本实例】
* ⑤ 把 endpoint / bucket / instance_role 填到下面,use_internal_endpoint 保持 true,enabled = true
* ⑥ 后台「站点设置 → 存储体检」确认桶连通性为 ✅
* (代理架构下【不需要】配置 CORS,也【不需要】绑定自定义域名)
*
* ★ 前提:站点必须运行在【阿里云 ECS】上并且已绑定 RAM 角色;
* 未绑定则取不到凭证,上传接口会明确报错(其它功能不受影响)。
*/
'oss' => [
'enabled' => false, // 填好参数后改为 true 才启用上传
'endpoint' => 'oss-cn-qingdao.aliyuncs.com', // 不含 http://;填公网或内网均可,程序按下方开关自动转换
'bucket' => 'your-bucket-name',
// ★ ECS 实例 RAM 角色名 —— 必须与【ECS 实例实际绑定的角色名】逐字一致。
// 查法:curl http://100.100.100.200/latest/meta-data/ram/security-credentials/
// 注意:RAM 里"创建的角色名"和"授予实例后实例上生效的角色名"可能不同(如
// 建的是 MyOssRole,授予实例后生效名可能是系统预置策略对应的角色),以查询结果为准。
'instance_role' => '',
// ★ 是否用内网 Endpoint(默认开)。开启后程序自动把 endpoint 改写成 *-internal 形式,
// 走阿里云内网访问 OSS:免公网流量费、延迟更低,且公网完全无法直连该地址。
// ⚠️ 仅当【桶与本机 ECS 同地域】时可用;跨地域时内网不可达,必须关掉本开关
// (回落公网 Endpoint)或改用同地域的桶。后台「存储体检」会探测并提示。
'use_internal_endpoint' => true,
// ---- 以下一般不用改 ----
'metadata_url' => '', // 留空=http://100.100.100.200(ECS 内网元数据服务)
'metadata_v2' => false, // true=加固模式(先取一次性 token 再读元数据,防 SSRF 探测)
'metadata_ttl' => 600, // 加固模式 token 有效期(秒,最长 21600)
'credential_cache' => '', // 凭证缓存文件;留空=与数据库同目录的 oss.sts.php
'credential_refresh_margin' => 300, // 到期前多少秒就提前换新(秒)
'domain' => '', // 代理架构下【不再使用】:附件一律经本站 media.php 输出,无需自定义域名
'url_scheme' => 'https', // 公网 Endpoint 时使用的协议(内网 Endpoint 强制走 http)
'prefix' => 'fb', // 对象键前缀:fb/2026/09/xxxxxxxx.jpg
'visibility' => 'private', // 记录桶的读写权限:代理架构下推荐 private(浏览器永不直连桶)
'sign_ttl' => 600, // 服务器内部签名 URL 的有效期(秒),仅服务器侧使用
'link_ttl' => 604800, // 兼容保留;代理读取时签名有效期由代码收敛到 ≤300 秒
'image_process' => true, // 列表缩略图使用 OSS 图片处理(服务端缩放后再转发,省下行流量)
'verify' => true, // 提交时 HEAD 复核对象(防伪造/防超限)
'host_override' => '', // 仅特殊网关/联调使用;留空自动按 bucket.endpoint 拼接
],
/* ---------------- 附件限额 ----------------
* 单位:字节。原图上限指「压缩前」的原始文件大小(前端会先压缩到最长边 1600px)。
*/
'upload' => [
'image_max_per_post' => 4,
'image_max_bytes' => 5242880, // 5 MB / 张
'image_exts' => ['jpg', 'jpeg', 'png', 'webp', 'gif'],
'image_mimes' => ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
'image_max_side' => 1600, // 前端压缩后的最长边(像素)
'video_max_per_post' => 1,
'video_max_bytes' => 52428800, // 50 MB / 个
'video_exts' => ['mp4', 'mov', 'webm'],
'video_mimes' => ['video/mp4', 'video/quicktime', 'video/webm'],
'total_cap_bytes' => 1073741824, // 全站附件总量上限 1 GB,达到后暂停接收新上传
'retention_days' => 365, // 附件保留天数,超期由 cron.php 清理
'upload_per_hour' => 60, // 每 IP 每小时最多申请多少次上传凭证
],
/* ---------------- 反垃圾 ---------------- */
'rate_limit' => [
'thread_per_hour' => 5, // 每个 IP 每小时最多发起的主题数
'reply_per_hour' => 20, // 每个 IP 每小时最多回复数
],
'post_min_seconds' => 3, // 打开表单到提交不得少于该秒数(过快视为机器人)
/* ---------------- 列表 ---------------- */
'page_size' => 20,
];