laoyang
|
832f112843
|
筛选区改成固定两行,分类再多也不会把表单挤走
用户:「筛选的时候,反馈类型和后面的下拉框不在一条线上;
如果反馈类型比较多,下拉框就被挤到下一行去了。
建议统一成分两行,这样也就没有对齐的问题了。」
问题根因:
.filters 是 display:flex + flex-wrap,分类 chips 和筛选表单被当成
同一行的兄弟元素,分类一多就把表单挤下去;
而且两者高度形态不同(chips 是胶囊、表单是控件),本来也对不齐。
改法:固定两行,各管各的
第 1 行 = 反馈类型 chips(内部可换行,不影响别的)
第 2 行 = 状态 / 排序 / 搜索框 / 搜索按钮(同一行,高度一致)
.filters 改 flex-direction:column。
关键点:文件里有两处 .filters 定义,后面那处(管理后台用)
是 display:flex + flex-wrap,会把前台的两行布局覆盖回横向。
已删除那处的 flex 声明,只保留对内部控件的宽度约束。
后台筛选栏是纯控件横排、没有分类 chips,不能跟着变竖排,
所以给 admin.php 的筛选表单加了 .filters-row 类,
用 .filters.filters-row 覆盖回 row。
验证(真实页面 + 注入 16 个额外分类,共 21 个):
视口 1280px:chips y 109→276,表单 y 286(在下方)✅ 表单内控件 1 行
视口 900px:同上 ✅
视口 700px:同上 ✅
视口 500px:chips y 103→400,表单 y 410(在下方)✅
表单内部换成 2 行 —— 窄屏放不下 4 个控件属正常响应式
前台 .filters flex-direction = column
后台 .filters.filters-row flex-direction = row,控件全在同一条线上
(垂直中心统一 92.8)
5 个路由 200
另外修掉一个隐患:改 .filters 时误删了 .chips 的基础 flex 样式,
已补回(.chips{display:flex;flex-wrap:wrap;gap:8px})。
|
2026-11-04 20:31:17 +08:00 |
|
laoyang
|
26d00ea981
|
后台顶栏去掉「我要反馈」「三横线选软件」「问题列表」
用户问:「管理后台还需要我要反馈这个按钮吗?还需要右上角那三个横线的设置按钮吗?
还需要拉下来后选择不同的软件吗?这些是不是都不需要了呀?」
确认都不需要,而且原来确实都在:
后台调 fb_layout_head() 时没传 software,走「无软件」分支,
结果顶栏照样渲染了问题列表 / 我要反馈 / 三横线,三横线里还列着
「轻反馈、同步日历、密码生成器、反馈测试」四个软件 —— 在后台点它毫无意义。
理由:
① 「我要反馈」是给访客的入口,管理员不该从后台跳去前台表单;
② 后台是跨软件视图(待审核/全部问题本身就带软件筛选器),
再放切软件菜单是重复的,切了也不会改变后台视图;
③ 「问题列表」依赖 ?sw= 当前软件上下文,后台没有这个上下文,
点了只会跳到前台某个软件,属于误导。
改法:fb_layout_head() 新增 [admin] 开关,admin.php 两处
(管理登录、管理后台主界面)都传 admin=true。
后台顶栏只留:站点标识(回首页)+「去前台看看」。
唯一的后台出口是「去前台看看」,管理员偶尔需要核对前台实际效果。
验证:
后台顶栏链接只剩 brand-mini(href=./) 和 navlink(href=index.php, 去前台看看);
sw-toggle / sw-panel 关键字均已不存在。
前台顶栏三样仍在(问题列表 / 我要反馈 / 三横线),未被波及。
6 个路由全 200。
|
2026-11-03 21:11:51 +08:00 |
|
laoyang
|
974dd52367
|
去掉软件标签的描边;修「全部问题」列表分类标签渲染成空
两个问题:
一、两个标签中间那道「线」
不是 CSS 分隔符 —— 是 .badge.sw(软件徽章)带 border:1px solid var(--swc-line),
而 .badge.cat / .badge.st 都没有边框。并排显示时那道描边看起来
就像凭空插了一条分隔线。去掉边框,三个标签统一为无边框胶囊。
二、反馈类型(Bug / 功能建议)在后台「全部问题」列表里不显示
🔴 根因:fb_category_label() 参数是**数字 id**,内部会 (int) 化。
而全部问题列表传的是 $t["category"](老 slug 字段,值为 bug),
(int)bug === 0 → 函数返回空串 → 渲染出一个**空的** badge:
标签框在、里面什么都没有,看起来就像「类型没了」。
修法:
- 新增 fb_category_label_by_slug($softwareId, $slug) 给老数据兜底
(slug 在不同软件下可能重名,必须带 software_id)
- 全部问题列表改用 category_id 优先、slug 兜底
- 前台列表同样处理,并且**取不到名字就不渲染这个标签**,
绝不再输出空胶囊
- 在 fb_category_label() 的注释里写明「必须传数字 id」,
避免下次再踩
验证:
函数对照跑通:#4 category_id=6 → Bug;#5 category_id=7 → 功能建议;
旧写法 fb_category_label("bug") → (空),确认这就是根因。
浏览器实测详情页三个标签 border-top-width 全部 0px / style none,
圆角都是 999px,描边已消除。
前台列表两个条目分类标签分别渲染出 Bug / 功能建议,不再是空。
7 个路由全 200。
|
2026-11-03 20:50:00 +08:00 |
|
laoyang
|
dcf510ef28
|
后台三处列表/详情:标题顶头独占一行,标签下移(与前台统一)
用户:「管理后台的待审核和全部问题那个列表里面也是一样的。
标题应该顶头,然后在标题下面另起一行,写上来自于哪一个软件、
它的状态是什么、它是什么分类。点到一个具体的反馈里面也是一样的,
标题顶头,然后把同步日历、bug、待处理往下一行。管理后台也是全部按照这种方式去处理。」
改三处:
1. fb_admin_pending_row(待审核列表)
prow-top(标签在前+标题)→ prow-head(标题独占一行)+ prow-tags(软件/分类/状态)
2. 全部问题列表
item-top → item-head + item-tags
🔴 同时补上软件名(fb_software_chip)—— 后台是跨软件视图,
前台某个软件内部不需要显示软件名(满页同一个),后台必须显示。
3. 后台详情页头部
item-top → item-head + item-tags,标题字号 19px 略大于列表
样式新增 .prow-head / .prow-tags(.prow-top 保留给操作日志,
日志条目只有「动作+编号」,单行反而合适)。
验证(真实渲染 + 浏览器测量,不登录后台、不动数据):
待审核行 标题 top=87 left=222 / 标签 top=119 left=222 → 标签在下方、左对齐 0px 偏差
全部问题 标题 top=318 left=223 / 标签 top=351 left=223 → 同上
详情头 标题 top=541 left=228 / 标签 top=575 left=228 → 同上
另外静态检查:三处的 head 段内均无状态标签,tags 段内均含软件/分类/状态。
前台 5 个路由 200,未受影响。
|
2026-11-03 18:58:04 +08:00 |
|
laoyang
|
28d28ad51c
|
软件徽标带图标+主题色;标题用软件色;删冗余欢迎语;抽屉改竖排覆盖
用户四点要求:
1. 待审核标题前的软件名要带图标
2. 每个软件配了颜色,徽标与标题都应显示该颜色
3. 「欢迎在这里反馈使用问题、提出功能建议」这类话没意义,全部删掉
4. 右上角三横线抽屉:不要宽屏横排/窄屏竖排两套,
统一竖排并把下面内容盖住,简单一点
改动:
- 新增 fb_software_chip():软件徽标带图标 + 该软件主题色
(原来一律固定蓝色 #eef2f8/#2b4a74,等于白配了颜色)
配 fb_color_soft()/fb_color_line() 由主色推导同色系浅底与描边
替换 admin.php 两处、thread.php 一处(原来是 badge 套 badge,双重边框)
- 顶栏标题与内容区标识都用软件主题色(PHP 内联 --swc 变量)
- index.php 不再渲染 site_desc(配置项保留,后台仍可编辑)
- 抽屉改为统一竖排 absolute 浮层 + 遮罩盖住正文,删掉宽屏横排分支
打开时锁背景滚动,点遮罩/Esc/再点按钮都能关
.topbar 作为定位参照(sticky 即可)
顺手修:fb_hex_to_rgb 的三位简写展开前要先校验字符,
否则 zzz 这类非法值可能被误展开(本次测试用例选错才发现校验不够严)
验证:3 个软件 chip 的图标/名字/主色/浅底/描边全部正确,
无图标退回字符、XSS 转义通过、颜色工具函数 6 项通过;
CDP 真实浏览器在 390px 与 1280px 两种宽度下各 13 项断言全过
(绝对定位、在顶栏下方、铺满宽度、条目纵排同x、y递增、
层级高于正文、遮罩同步显隐、可关);
三个软件标题色分别 #b4553c / #6a4c93 / #0057b8;全部路由 200。
|
2026-11-03 09:20:14 +08:00 |
|
laoyang
|
314a1cf2a5
|
新增「未公开」状态;待审核改列表式,只留通过并公开
用户要求:
1. 待审核/全部问题都用列表形式
2. 待审核去掉「关闭说明」整块,后面只加一个「通过并公开」按钮
3. 标题本身就是进管理页的链接,去掉「进入管理」
4. 「未公开」应当是一种状态:提交后初始状态不是「待处理」而是「未公开」
状态流转(用户定义):
提交 → 未公开
├─ 通过并公开 → 待处理 → 处理中 → 已解决 → 已关闭
└─ 直接关闭 → 已关闭(不经过待处理)
每次状态变更、每次有人回复,都给提问人发信
实现:
- statuses 新增 hidden「未公开」,sort=0 排最前,其余顺延
幂等迁移:老库检测到没有 hidden 就整体后移一位再插入,
并把 visible=0 且 status=open 的存量数据归到 hidden
- new.php 初始状态按可见性决定:未公开→hidden,公开→open
(以前一律写 open,后台「待审核」里显示的全是「待处理」,状态反映不出事实)
- approve 动作:visible=1 且 hidden→open,并用更新后的行渲染邮件
- 详情页「通过并公开」按钮原来传原状态(未公开时通过后仍是未公开),改为固定传 open
- 待审核查询改为 status=hidden(兼容老数据 visible=0 且 open),
不再用 visible=0 AND status<>closed —— 那个条件会把「已关闭但未公开」也收进来
- 新增 fb_admin_pending_row() 列表行渲染:标题即链接、右侧单个按钮
- 删除 fb_admin_thread_card() 及「关闭说明」表单
- 新增 .plist/.prow 列表样式与 .st-hidden 状态徽标(中性灰)
- mailer 补 hidden 状态的文案,避免落到兜底
验证(线上真实数据):6 个状态齐全、提交落 hidden、
待审核能查到且不含已公开、通过后转 open 并移出待审核、
后续 doing/resolved/closed 推进正常、6 个状态邮件均能渲染、
待审核行结构 10 项断言全过(无关闭说明/无 note/无进入管理/标题可点)。
测试数据已清理,用户原有 4 条数据完好。
|
2026-11-03 09:09:40 +08:00 |
|
laoyang
|
db8c1969af
|
文案:全站「你」改为「您」
用户要求邮件模板统一用敬语,确认后网页也一并改。
范围:
- lib/mailer.php 61 处(全部邮件模板:回执/回复/状态/审核/关闭/不公开/新反馈)
- new.php 6 处(提交页提示、占位文字)
- thread.php 4 处(提交成功提示、回复框占位)
- lib/layout.php 1 处(页脚隐私说明)
- admin.php 5 处(测试邮件正文、关闭邮件主题模板、通知规则说明、
先审后显说明)
已核查无「您们」等机械替换产生的别扭表达;
邮件模板渲染后「你」零残留、「您」77 处,文案通顺。
线上四个页面可见文字均无「你」,全站路由 200。
|
2026-11-03 09:00:05 +08:00 |
|
laoyang
|
738c6c09ff
|
多软件区分:邮件按软件换色+图标+软件名;补提交回执邮件
用户要求:多软件并存时,用「颜色 + 图标 + 名字」三重区分,
邮件也要跟着所属软件变,一眼能看出是哪款软件的来信。
1. software 表加 icon / download_url 字段(幂等迁移)
- icon 存单个字符(emoji / 汉字 / 字母),不存图片路径:
前端与邮件用同一个字符渲染,天然一致,不用再管一套上传与权限
- 没配 icon 时取名首字兜底,任何地方都不出现空图标
- 后台软件编辑加图标快选(12 个常用 emoji)
2. 邮件配色改为跟随软件(fb_mail_brand 取 fb_software_colors)
- 头部色条、按钮、引用块色条、徽标全部跟着软件走
- 邮件头部改为「图标色块 + 软件名」,下面再小字标站点名
- 主题行统一加【软件名】前缀,不点开就能分辨
3. 新增 receipt 事件:提交后立即给提问人发「已收到、待审核、请耐心等待」
- 独立开关 notify.receipt_enabled(与「回复通知」是两件事)
- 新增 7 处调用前的静默 return 也一并写日志
4. reply 邮件补上下文:原提问摘要 + 本次回复,像博客评论通知
5. status=resolved 文案改为「已完成并上线」,配了下载地址时给下载按钮
验证:多软件配色/图标/前缀全部通过(含反向验证),XSS 转义通过,
空图标兜底通过;页面顶栏、页头、选择器图标渲染正确。
|
2026-11-03 08:00:50 +08:00 |
|
laoyang
|
e43f2ce67a
|
修复:邮件通知两处静默失效(测试暴露)
症状:notify_log 全表为空 → 用户报「完全没发邮件」,后台查不到任何线索。
1. 前 4 个前置判断(开关/收件人/自己回自己/冷却)直接 return 不写日志,
等于「为什么没发」完全无痕。改为任何退出路径都写 notify_log,
并带上可读原因。
2. new 事件(新反馈通知管理员)收件人只取 admin_email,该值为空时整条
通知静默失效 —— 管理员永远收不到新反馈信,界面也无任何提示。
改为回落 smtp.user / smtp.from;后台勾了「通知管理员」却没填邮箱时
当场显示警告。
验证:走 fb_notify_event 全事件测试,6 个事件全部写日志、new 能送达。
|
2026-11-03 07:31:35 +08:00 |
|
laoyang
|
8a850ca0e5
|
修复:全站图片 503 —— oss.enabled 派生写入被 has() 守卫拦掉
问题:attachment.storage 存成 oss,但 oss.enabled 一直停在 0。
media.php 判 fb_oss_enabled() 为 false,直接 503「对象存储未配置」,
所有 OSS 附件(历史 + 新增)全部打不开。
根因有两层:
1. oss.enabled 是派生字段,表单里没有对应 input,上一个提交加的
$save() 统一守卫用 $has() 判断「没提交」就 return,把它挡掉了;
2. 更根本的是两份状态描述同一件事 —— storage 和 enabled 迟早漂移。
修法:
- 派生写入改用 $saveForce(),并整段包进 if ($has(attachment.storage)),
避免反向问题(没提交存储方式时被默认值改成 off);
- fb_oss_enabled() 改为只看 attachment.storage 作单一事实来源,
oss.enabled 仅在 storage 完全没配过的老库上作为回落。
|
2026-11-03 07:21:04 +08:00 |
|
laoyang
|
605640aa90
|
根治:配置被跨页面保存清空(第三次复发,改为强制守卫) (#2)
## 用户的原话
「为什么我的配置,你总是改掉?到底在干什么?我配的 OSS,好几次都让你给清空了。」
## 先给结论:没有重置数据库
installed_at 仍是 11-01 15:14,oss.* / site_name / admin.username 全都在。
但 **OSS 配置确实又被清空了**,而且这是我的问题,第三次。
## 时间线(这次是旧代码干的)
22:53:36 用户点「保存设置」→ 日志记录:存储=off
22:57:10 我的修复才部署上线
晚了 3 分 34 秒。所以那次仍是旧代码。
但这不能当借口 —— 同一类 bug 已经复发三次:
11-01 23:20 storage=off
11-02 09:01 之后被清
11-02 22:53 storage=off
## 为什么反复修不好:一直在治标
前两次我的做法是「给用户点出来的那个字段补 has() 判断」。
但保存函数里有 **32 处 fb_set_setting,只有 8 处手写了 has()**。
漏掉任何一处,就是一次事故。这是结构性缺陷,不是个别疏忽。
## 这次的做法:从写入入口上锁
新增 $save() 强制守卫,**本文件唯一允许写设置的入口**:
$save = function ($k, $v) use ($has) {
if (!$has($k)) { return false; } // 铁律:没提交就不动
return fb_set_setting($k, $v);
};
把保存函数体里全部 32 处写入统一改为 $save()。
现在除了守卫自身内部,**没有任何地方能绕过「字段没提交就不写」**。
另外补上两处最关键的漏网:
- smtp.enabled 原先无 has(),站点设置页保存会把它写成 0
- smtp.from 兜底逻辑会在邮件页之外触发,改为仅在 from 被提交时生效
## 验证:写了一个专门抓这类问题的测试
`_tooling/feedlite/fb_config_guard_test.py`,6 项:
A 站点设置页保存 → 邮件配置必须全部不变
B 邮件页保存 → OSS/附件配置必须全部不变
C 反复交叉保存 3 轮 → 全部配置始终不变
D 提交残缺表单 → 绝不能把其它配置写空
正常代码 6/6 通过。
## 反向验证(关键)
把 $save 退化成直接写库、并去掉两个分组判断后重跑:
A1 FAIL smtp.enabled:1→0; notify.enabled:1→0; ... ← 邮件没了
B1 FAIL attachment.storage:oss→off; oss.enabled:1→0; ... ← OSS 没了
C1 FAIL 3 轮后同样被清
D1 FAIL 残缺表单把 storage 写成了 off
现象与用户遇到的一模一样,证明测试确实能抓到,不是假通过。
## 回归
全新安装 33/33、邮件专项 73/73;前台各页状态码正常、无 PHP 错误。
(老基线 fb_e2e_wsl.py / fb_attach_wsl.py 两个脚本已不在仓库,
属历史遗留,本次以全新安装 + 邮件专项 + 配置守护三套覆盖。)
|
2026-11-02 23:36:26 +08:00 |
|
laoyang
|
4033170ad7
|
修复:跨 tab 保存互相清空配置;补齐全流程邮件通知与统一模板 (#2)
## 一、用户报「我明明配过 SMTP,怎么全丢了、信也发不出」
**没有重置数据库**(installed_at 仍是 11-01 15:14,oss.*/site_name/admin.username 全在)。
真因是我上一轮「邮件拆独立 tab」挖的坑:站点设置与邮件通知**共用 act=settings**,
而保存函数把 smtp 字段无条件写库:
foreach ($smtpText as $k) {
fb_set_setting($k, trim((string)$post($k, \x27\x27))); // 没提交 → 空串 → 覆盖
}
在站点设置页保存附件设置时,该表单没有 smtp_* 字段,$post() 取到空串,
于是 host/user/from 被清成空。smtp.pass 因为单独做了「留空=不修改」而幸存,
所以现场是 pass 有值、其余全空 —— 这个组合本身就是 bug 的指纹。
修法:新增 $has() 判断「本次请求是否真的提交了该字段」,没提交就跳过。
按 tab 分两组保护:
- $isSiteForm:站点信息/页脚/附件/OSS,逐字段 has() 双保险
- $isMailForm:SMTP/通知开关,整段 + 逐字段
checkbox 尤其要保护:未提交会被当成「取消勾选」,
在站点设置页存一次就会把「有人回复时通知」全关掉。
## 二、notify.admin_new_thread 是假的(用户说「提交了反馈收不到邮件」)
这个设置项:白名单里有、后台有勾选框,**但没有任何代码实现它**。
grep 只命中三处(白名单/保存/界面),没有一处真正发信。
已在 new.php 提交成功后实现:查新帖 → 取所属软件名 → fb_notify_event(\x27new\x27)。
## 三、补齐全部事件,统一入口
此前只有 reply 和 close 两个事件,且两套模板风格不一致。
新增 fb_notify_event() 统一入口,处理「开关→收件人→节流→发信→日志」。
覆盖 8 类事件:reply / status / approve / hide / close / new。
- status 按状态给不同文案(待处理/处理中/已解决/关闭/不予考虑),
「不予考虑」语气克制、必须带理由并留申诉入口
- approve / hide 原先完全不发信,现在补上
- 状态没变化时不发信(不打扰用户)
## 四、邮件模板重做
抽出 fb_mail_layout() 统一外壳,所有事件共用:
- table + 内联样式(Outlook/QQ邮箱/Gmail 兼容)
- 600px 卡片、顶部主色条、圆角徽标、字段表、引用块、callout、CTA 按钮
- 响应式(已验 375px 无溢出、无裁切)
- preheader 摘要、纯文本兜底链接、收件人提示
## 五、后台状态按钮(用户要求)
问题详情页顶部一排按钮,点一下立刻生效并自动发信:
「通过并公开」「处理中」「已解决」「不予考虑」「撤下」
- 当前状态对应的按钮置灰
- 「不予考虑」展开内联面板填理由(不用 window.prompt)
- 新增 setstatus 动作;「通过并公开」会同时置可见
## 六、notify_log 加 reason 列
原表只有 reply_id,无法区分事件类型。加 reason 并幂等迁移;
老库 reply_id 是 NOT NULL,非回复事件用 0 表示。
## 验证
邮件专项 73/73:
- 8 类事件模板全部完整 HTML、table 布局、徽标、页脚、无 script
- 8 个主题互不相同;状态徽标文案正确
- 总开关关闭时 admin_new_thread 仍可独立生效
- 节流只作用于 reply/status,close 不受影响
- SMTP 未启用时留失败日志(含 reason 与原因)
- C1-C5:站点页保存后 smtp.host/user/notify.enabled 均未被清空;
邮件页保存后 oss.bucket 未被清空
反向验证:完整回退两层保护后 C1/C2/C3 全部 FAIL
(host 从 smtp.qiye.aliyun.com 变成空、notify.enabled 从 1 变 0),
证明测试能真正抓到该 bug,不是假通过。
回归:全新安装 33/33、老基线 25/25、附件 22/22。
|
2026-11-02 22:57:00 +08:00 |
|
laoyang
|
74beaf1163
|
附件限制全部支持填 0 = 不限制 (#4)
用户要求:附件存储里所有限制项(几张图、几个视频、大小、总量、保留天数、
每小时次数)都能填 0,0 表示没有任何限制。
## 最危险的地方:填 0 原本是「全禁」不是「不限」
两处逻辑把 0 判成了禁止,语义正好相反:
1. `fb_rate_check()` —— `count < limit`,limit=0 时 `0 < 0` 为假
⇒ 每小时上传次数填 0 = 一次都不许传
2. 每帖数量校验 —— `count > max`,max=0 时传一个就超限
⇒ 每帖图片填 0 = 一张都不许传
都改成「limit <= 0 直接放行」。
## 校验区间与 input 下限全部放开到 0
| 字段 | 原下限 | 现下限 |
|---|---|---|
| image_max_per_post | 0(但界面上限 20) | 0,上限 1000 |
| video_max_per_post | 0(上限 10) | 0,上限 1000 |
| image_max_bytes | 0.2 MB | 0,上限 1024 MB |
| video_max_bytes | 1 MB | 0,上限 10240 MB |
| total_cap_bytes | 10 MB | 0,上限 1048576 MB |
| retention_days | 1 天 | 0,上限 36500 |
| upload_per_hour | 1 次 | 0,上限 100000 |
| image_max_side | 480 | **保持 480** —— 这是压缩目标边长,不是限制项 |
服务端 `max($range[0], ...)` 不再把 0 拉回下限;input 的 min 一并改 0,
否则浏览器直接拦住连提交都到不了后端。
## 语义冲突:0=不限与「0=关闭该类上传」冲突
原先 `fb_upload_kind_on()` 靠「数量上限是否为 0」判断开关,
0 现在表示不限个数,两者冲突。
按用户选择:**新增独立总开关** `upload.image_on` / `upload.video_on`(默认开),
`fb_upload_kind_on()` 改看开关,不再看数量。
两个新键已加入 `fb_setting_keys()` 白名单(这个坑踩过多次)。
## 前端同步
- `maxCount > 0` 才拦,0 不拦
- 提示文案:数量 0 显示「数量不限」、大小 0 显示「大小不限」,
不再出现「最多 0 张」这种荒唐文案
## 风险提示(按用户选择用文字告知,不阻止保存)
单张图片/单个视频/全站总量填 0 时,字段下方提示「填 0 时注意:磁盘可能被写满」。
## 验证
0 专项 31/31,关键几条:
- 七项输入框 min 均为 0,image_max_side 保持 480
- 填 0 后数据库里就是 0,没被拉回下限
- **填 0 后行为是放行**:每小时放行、数量不限、总量放行、不清理
- 数量填 0 时上传仍是开的(靠独立开关)
- 取消勾选开关后确实关闭,且与数量填 0 互不干扰
- 非 0 值全部正确(4 张 / 2 个 / 5MB / 50MB / 1024MB / 30 天 / 60 次)
回归全绿:全新安装 33/33、附件 22/22、老基线 25/25、安装向导 60/60、约束检查 20/20。
|
2026-11-02 08:55:23 +08:00 |
|
laoyang
|
b0e6ac9cd8
|
邮件 tab 输入框不再预填出厂示例值 (#2)
用户反馈:「邮件通知这个页面里的那些输入框,当我鼠标点完之后,还是没有把里面的内容删除。」
上一轮(#3)只处理了 prefix 和邮件标题两个字段,**漏了 smtp 整段**。
根因同前:config.example.php 的 smtp 段有全套出厂示例值,
fb_cfg() 合并后表单把它们当"已填内容"显示,用户点进去得先删一遍。
## 本次处理
全部改用 fb_form_val():数据库里没设过 = 未自定义 = 显示空,示例只留 placeholder。
| 字段 | 出厂值 | 现在 |
|---|---|---|
| smtp.host | smtp.example.com | 空,placeholder 给真实可用的 smtp.qiye.aliyun.com |
| smtp.user | noreply@example.com | 空 |
| smtp.from | noreply@example.com | 空,placeholder「留空则同登录账号」 |
| smtp.from_name | 轻反馈 | 空,placeholder 显示当前站点名 |
| smtp.port | 465 | 空,placeholder 465 |
| smtp.timeout | 15 | 空,placeholder 15 |
| notify.cooldown_seconds | 300 | 空,placeholder 300 |
## 顺带修两个真问题
1. **留空不能存 0**:端口/超时/冷却原先 `(int)$post()`,留空会变成 0,
0 端口根本连不上、0 超时立刻断。改成留空即删键,回落 config 默认值。
2. **cooldown 出厂值是 300 不是 60**:我一开始按 60 判,导致 300≠60 被当成
"用户设过"而回显。核对 config.example.php:80 后改正。
⚠️ 注意 cooldown 的 0 是合法值(表示不限制),所以用「空串」区分"没填",
不能用 0 判断,否则用户真想设 0 会被当成默认。
## 验证
邮件预填专项 33/33:
- 7 个字段 value 全空,示例在 placeholder
- 无任何出厂示例值被预填
- 真实值保存后逐项回显(含 0 这个边界)
- 留空后回显空 + 数据库键被删 + 生效值回落 465/15/300
- 填 0 被正确保留
回归全绿:全新安装 33/33、安装向导 60/60、老基线 25/25、邮件 tab 专项 37/37。
|
2026-11-01 19:26:42 +08:00 |
|
laoyang
|
367f78f239
|
站点设置拆出独立「邮件通知」标签页 (#2)
用户反馈:「站点设置现在太长了,应该把邮箱设置单独拿出来,做一个标签页。」
原先一个 settings tab 里塞了:站点信息、页脚、邮件通知、附件存储、审核、
存储体检。光邮件那段就 82 行(10 多个字段 + 测试按钮),往下翻很累。
改动:
- 后台导航新增「邮件通知」tab,放在「站点设置」左边
- 邮件那段界面整体搬到新 tab(SMTP 配置 + 通知规则 + 测试发送)
- settings 里彻底移除,不留两处
- tab 白名单加 mail
- 新 tab 有自己的页头(h1 邮件通知)和「← 回站点设置」返回入口
保存逻辑没动:新 tab 的表单仍提交 act=settings,走的还是原来那段
处理代码,所以 SMTP / notify 的写入行为完全不变。
## 兼容
旧链接 admin.php?tab=settings 仍然有效,只是里面不再有邮件字段。
邮件配置数据没动,新 tab 打开就能看到原值。
## 验证
邮件 tab 专项 37/37:
- 6 个 tab 全部 200
- 导航有入口、文字正确
- settings 里 smtp_host/port/pass 均 0 次,无测试按钮
- mail tab 11 个字段齐全 + 总开关 + 页头 + 返回入口
- 保存后回显:服务器、端口、冷却秒数正确;口令显示「已设置」
- 两个 tab 互不干扰
回归全绿:全新安装 33/33、安装向导 60/60、老基线 25/25。
|
2026-11-01 17:29:32 +08:00 |
|
laoyang
|
e408dc78b8
|
表单不再预填示例数据,示例只留 placeholder (#3)
用户反馈:输入框里预填的「示例值」点进去得先删干净才能填自己的,不合理。
必须分清两类,否则会误删真实配置:
- A 类 真实配置(从数据库读出来的)→ 保留
- B 类 出厂示例(代码/配置文件里写死的)→ 清空,示例移 placeholder
本次处理的 B 类:
1. 安装页「网站名称」
原先回显 config.example.php 里的出厂名「轻反馈」,用户以为要保留。
现在首次进入为空,仅在表单校验失败重来时回填用户刚才填的内容。
2. 安装页「管理员用户名」
原先预填 admin,现在为空,placeholder 提示「例如 admin」。
3. 后台「对象键前缀」
原先显示 fb(来自 config 默认值),现在为空,placeholder「留空即用 fb」。
4. 后台「关闭通知邮件标题」
原先显示模板「关于你反馈的问题《%s》」,现在为空,placeholder 给出模板。
## 新增 fb_form_val()
config.example.php 里给的出厂默认值和「用户填过的值」在 fb_cfg() 里合并后
分不出来 —— 表单永远显示得出内容。这个函数按「数据库里有没有这条设置」判定:
设过就回显(哪怕值恰好等于默认),没设过就当未自定义、显示空。
## 顺带修复一个真 bug
close_mail_subject 原先留空会存成空字符串,fb_cfg() 返回空串后
sprintf() 得到空邮件标题 —— 用户清空模板就发不出有主题的邮件。
改成留空即删除该设置,回落到默认模板。
## 验证
#3 专项 20/20(含:真实配置保存后必须回显、清空后必须回落默认且不存空串);
回归全绿:全新安装 33/33、安装向导 60/60、老基线 25/25、约束检查 20/20。
|
2026-11-01 16:53:52 +08:00 |
|
laoyang
|
f1a81cbaa1
|
RAM 角色自动探测 + 邮件配置界面 + 每个软件独立主题色 (#1 #2)
## 1. ECS RAM 角色改为自动探测
原先要用户去阿里云控制台查「实例 RAM 角色名」再手填,纯属多余 ——
机器自己知道答案。
新增 fb_oss_detect_instance_role():读元数据
GET /latest/meta-data/ram/security-credentials/(末尾带斜杠)拿到角色名列表。
- 兼容单行 / 多行 / JSON 数组三种返回
- 未配置角色时 fb_oss_instance_creds() 自动探测,不再报「未配置」
- 后台改成只读展示「已绑定:xxx」,手填入口收进「高级」折叠里
- 保存时留空 = 自动探测后写入,不存空值
- 探测失败给出可操作原因(没绑角色 / 不在 ECS 上 / 元数据被禁用)
## 2. 邮件配置界面(原先完全缺失)
lib/mailer.php 有完整 SMTP 实现,但后台一个输入框都没有,
只能改 config.php —— 用户装完根本找不到地方配。
根因有两层:
- fb_setting_keys() 白名单里没有任何 smtp.* / notify.* 键,
就算加了表单,fb_set_setting() 也会把值全部丢弃
- 设置页只有「站点信息」「附件存储」两个区块
补上:白名单 15 个键 + 后台「邮件通知」区块
- SMTP 服务器/端口/加密方式(SSL/STARTTLS/不加密)
- 登录账号/密码(留空不修改,避免每次重填)
- 发件地址/显示名、超时、SSL 校验
- 通知规则:回复通知、每次回复都通知、新反馈通知管理员、发信冷却
- 「发送测试邮件」按钮,AJAX 发完就地显示成功或失败原因,
不用保存再靠猜
## 3. 每个软件一个主题色
需求:从短链接进不同软件,颜色不同,一眼区分。
- software 表加 color 字段(幂等迁移)
- 全站配色靠 --brand / --brand-600 / --brand-soft 三个变量,
按软件覆写这三个即可,不用为每个软件写一套 CSS
- fb_color_variants() 由主色推出深一档(×0.85)与浅一档(往白混 88%)
- 未配色的软件按 id 从 8 色调色板取默认色,保证同站不撞色
- 后台:颜色选择器(原生取色 + 8 个预设 + 用默认)
- 前台:index / new / thread 都按当前软件注入主题色,
meta theme-color 也跟着变
验证:三功能专项 30/30;新旧回归全过
(全新安装 33/33、老基线 25/25、附件 22/22、安装向导 60/60、约束检查 20/20)
|
2026-11-01 16:39:09 +08:00 |
|
laoyang
|
c19e475e85
|
修复:附件总量上限填 10240 被浏览器拦住(step 与 min 冲突) (#1)
用户反馈「全站附件总量上限填 10240 不行」。
根因:input 写成 step="16" min="10"。
HTML 的 step 是从 min 起算的 —— 合法值只有 10、26、42、58…(10 + 16n)。
10240 不在这个序列里,浏览器直接判为非法值、拒绝提交。
更糟的是这不是个例:1024(1G)、2048、4096、8192 全都不合法,
只有 10/26/42 这种怪数字能填进去。这个 bug 从该字段上线就存在。
修法:
- step="16" → step="any",允许任意数值
- 补 max="100000",与服务端 [10, 100000] 区间对齐,
避免用户填 999999 被服务端静默截断成 100000
顺带排查另外两个容量字段,确认没事:
- 图片单张上限 min=0.2 step=0.1 → 0.2 起步正好对齐,0.5/1/5/10 都合法
- 视频单个上限 min=1 step=1 → 整数都对
只有总量那一个字段踩了坑。
新增 tools 级检查脚本(_tooling 侧 fb_input_constraint_check.py):
按 HTML 规范逐条验算每个 number 输入框的 min/max/step 是否放行
「默认值」与「常见取值」,并核对前后端区间是否一致。19/19 通过。
这类错误肉眼看不出来,必须用规则算。
验证:约束一致性检查 19/19;服务端 MB↔字节往返一致(含 10240)。
|
2026-11-01 15:26:08 +08:00 |
|
laoyang
|
9b822d2d9a
|
安装向导:加管理员用户名,精简为填表即装、装完进首页 (#1 #2)
## 管理员账号(用户名 + 口令)
原先只有口令,没有用户名。现在两者都有:
- 用户名词入库 settings 的 admin.username(2~32 位,字母数字下划线点连字符)
- 口令仍是 bcrypt 哈希存 admin.password_hash,明文不落任何文件
- 登录校验 fb_admin_verify_login():用户名或口令任一不对都返回同一句
「用户名或口令不正确」,且用户名错时也照常跑一次 password_verify,避免用耗时探测账号
- 向后兼容:老部署只设了口令没用户名时,仍可只填口令登录,
并在登录页提示补一个用户名
## 安装向导精简
按用户要求「没数据就初始化,填完进主页」:
- 必填只剩 4 项:网站名称、管理员用户名、口令、确认口令
- 站点描述 / 官方邮箱 / 站点根地址改为 hidden(可留空自动探测)
- 密钥、令牌原本就自动生成
- 安装成功后展示网站名称与用户名,2 秒自动跳首页,也有按钮
- 按钮文案改为「完成安装,进入首页」
## 修掉一个真实 bug
首页把软件说明当成站点描述,导致「软件有 note 时站点描述被顶掉」。
两者是不同的东西:站点描述是全局设置必须始终显示,软件说明另起一行补充。
老基线 D6「首页描述变为新描述」就是被这个 bug 弄失败的。
## 另一个坑
admin.username 忘了加进 fb_setting_keys() 白名单,fb_set_setting() 会静默丢弃,
表现为「安装时填了用户名,装完却没有」。已补。
验证:
- 全新安装流程 32/32(真空站:无 config.php、无 data/*.db)
- 老基线 25/25、附件专项 22/22、安装向导 60/60
- PHP 8.5 全部入口 0 弃用
|
2026-11-01 12:39:20 +08:00 |
|
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 |
|
laoyang
|
5006d54be3
|
附件读取统一走 media.php 代理,真实地址不再暴露
无论附件存在本机 uploads/ 还是 OSS 私有桶,前台给浏览器的地址现在完全一致:
media.php?k=<对象键>。界面上不再出现 uploads/ 真实路径,也不出现任何 bucket /
endpoint / 签名痕迹。
- lib/attach.php:新增 fb_attach_proxy_url(),fb_attach_url() 与
fb_attach_thumb_url() 的本机分支一并改走代理(地址不落库、渲染现算,
对历史附件立即生效,无需迁移)。
- uploads/ 整目录禁止 Web 直读:附件只能经 media.php 出,真实物理路径
即便被猜到也取不到,同时绕不开 media.php 的可见性鉴权。
- 规则版本化自动升级:.htaccess 内埋 `# fb-local-guard: vN`,
旧版规则在任意请求时由 PHP(目录属主)整份重写。之所以不能靠部署覆盖:
部署账号对 uploads/ 无写权限,只有程序自己能改。
- 随包 uploads/.htaccess 改为程序产出的逐字节副本,并加断言防漂移
(否则线上会被自动升级覆盖,随包那份形同虚设)。
- 顺带修复真实源码泄漏:config.php.bak_<时间戳> 这类「非 .php 结尾、
但含源码」的备份文件此前可被公开下载。加固根 .htaccess:
去掉正则结尾锚点、补 .php[.\-_] 兜底、点名拒绝 config.php / oss.sts.php。
- media.php / upload.php / admin.php / README.md:文档与响应头同步。
验证:php -l 19 文件 0 失败;主回归 25/25、附件专项 22/22、安装向导 60/60、
统一代理渲染 7/7、反向验证 V1-V4 全被抓住;线上冒烟 30/30、
线上统一代理真机 13/13(OSS 私有桶 + 内网 endpoint 下上传→代理读回→
Range 206→伪造键 403)。
|
2026-09-28 23:33:45 +08:00 |
|
laoyang
|
27187b38f1
|
页脚入口与安装向导;加固管理员口令存储
需求落实:
1. 页脚常驻「管理后台」链接,不用再手敲 admin.php。
2. 页脚「由 FeedLite 驱动」中的 FeedLite 改为超链接(指向开源仓库);
后台新增「页脚」设置组,可关闭整句(关闭后只留管理后台链接),也可改链接地址。
3. 管理员口令存储方式在后台如实展示:数据库 settings.admin.password_hash,
存 bcrypt 单向哈希(不可逆),明文不落盘。
4. 新增安装向导 install.php:data/ 下没有数据库时自动 302 引导;
体检环境 -> 填站点信息与口令 -> 生成 config.php -> 建库建表 -> 完成。
顺带修掉一个真实安全隐患:
config.php 里的出厂占位口令 CHANGE_ME_admin 原先可被当作有效口令登录后台
(等于公开仓库里就写着线上后门口令)。现改为占位/空值一律拒绝,
未设口令时后台明确提示去安装向导,不再含糊报「口令不正确」。
实现要点:
- 新增 fb_cfg_file():只读配置文件、不叠加 settings 表、绝不触达数据库。
「是否已安装」以数据库文件是否存在为判据,而 fb_cfg() 读 settings 会触发
fb_db() 就地建库 —— 那会把安装态自己造出来,所以判定必须走不碰库的通道。
- bootstrap 只在已安装时建库;未安装则 302 到 install.php(CLI 下报错退出)。
- fb_settings_flat() 在未安装时直接返回空,杜绝任何隐式建库。
- install.php 用文本级替换生成 config.php,保留全部注释与 OSS/SMTP 段落;
生成的 PHP 用 token_get_all(TOKEN_PARSE) 校验后才落盘。
- 页脚链接只放行 http/https,后台保存时校验(防 javascript: 伪协议)。
- style.css 页脚行改 flex,版本号 v6 -> v7。
验证:
- php -l 全量通过;现有回归 25/25、附件专项 13/13 保持通过。
- 安装向导专项 60/60(含「明文口令未写入任何文件」「数据库字节里搜不到明文」)。
- 反向验证:撤销三处修复后对应断言确实失败(断言非装饰)。
- 几何检查:本机 15/15 + 页脚专项全过;线上真实 URL 15/15 + 页脚专项全过。
- 线上真机冒烟 22/22。
|
2026-09-28 22:53:55 +08:00 |
|
laoyang
|
1401ad899b
|
OSS 附件改为私有桶+内网Endpoint+服务器代理架构
背景:桶为私有、且只用内网 Endpoint(外网不可达),浏览器既无法直传、
也无法直连。原「浏览器直传 + 签名URL直连」两条路都失效,改为全站代理。
lib/oss.php:
- endpoint 自动转内网(*-internal),use_internal_endpoint 开关控制,内网走 http
- 移除浏览器直传凭证(PostObject),改为服务器端 PUT(流式 fb_oss_put_file)
- 新增 fb_oss_stream_get 流式读取(回调式,支持 Range,不整文件进内存)
- 签名支持 x-oss-process 子资源 + Authorization 头签名(Content-Type 入签)
- 新增 fb_oss_probe 连通性自检、fb_oss_error_text 错误归因(含跨地域提示)
media.php(新增): 附件代理输出,鉴权边界=对象键须在本站附件表(或本人待入库
登记)且帖子对访客可见,防止本站沦为能读整个共用桶的开放代理;透传 Range。
upload.php: local/oss 统一走两步(领键→action=put),OSS 模式由服务器代理 PUT。
lib/db.php: 新增 pending_attachments 表 + idx_att_key 索引(迁移幂等自动建)。
lib/attach.php: 待入库登记(mark/clear/mine/gc),解决上传后入库前的预览鉴权。
cron.php: 清理过期待入库登记 + 桶连通性自检输出。
admin.php: 设置页加「使用内网 Endpoint」开关、存储体检显示实际连接地址与探测按钮。
README/config: 全面更新为代理架构(无需CORS、无需自定义域名、同地域要求)。
验证:本地 e2e 25/25 + 附件专项 13/13(含防开放代理/Range/内网改写);
线上真机上传→内网PUT→media.php代理读回→Range 全通过。
|
2026-09-28 22:12:28 +08:00 |
|
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 |
|