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
|
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
|
0365098fa4
|
修复:空格式白名单反而拒绝一切文件;定位「填 0 时正文图传不上」真因 (#4)
## 一、定位用户报的「提交反馈里图片不显示」
用户先说正文图不显示、回复的却显示,随后反馈「现在可以了,但之前确实不行」。
真因在 #4 已修掉,但当时没写清楚机制:
for (var i = 0; i < files.length; i++) {
if (countOf(kind) >= maxCount) {
alert("最多上传 " + maxCount + " 个");
break; // ← 整批文件一个都不传
}
}
数量上限填 0(本意不限)时,countOf 是 0、maxCount 是 0,
`0 >= 0` 为真 ⇒ 弹「最多上传 0 个」⇒ break 跳出整个循环 ⇒
选中几张就全部丢弃。上传区看起来"正常"(只是弹了个框),
提交后正文自然没有图。
已在线上核实用户新发的那条:aid=2 tid=2 reply_id=NULL,
附件正确挂在主题正文,详情页 .atts 渲染出 1 个 img,media.php 返回 200 / 225KB。
## 二、顺带修同一段里的第二个同类陷阱
var allowed = (box.getAttribute("data-accept") || "").split(",");
if (allowed.length && allowed.indexOf(file.type) < 0) { ... 拒绝 ... }
`"".split(",")` 返回 `[""]`,长度是 1 不是 0。
所以「未配置格式白名单」时,allowed.length 判真、indexOf 永远 -1
⇒ 空列表反而拒绝一切文件,这与"没配置=不限格式"的意图相反。
修法:split 后 filter 掉空项。实测:
normal : allowed=["image/jpeg","image/png"] len=2 blocked=false
empty : allowed=[""] len=1 blocked=true ← 旧代码
当前 UI 没有清空 mimes 的入口,现网触发不到,但属于同一类
「空/0 被当成最严格限制」的坑,一并修掉。
## 三、资源版本 14 → 15
🔴 上一轮就是改了 app.js 却忘升版本,浏览器一直用缓存旧 JS,
导致修复看起来"没生效"。这次改完立刻升,三处引用
(layout.php 的 css/js、install.php 的 css)都走 FB_ASSET_V 常量。
## 四、新增专项与反向验证
`_tooling/feedlite/fb_attach_zero_js.py`:19 项。
关键设计:数量判断条件**从源码正则抽取**后注入 node harness,
不在测试里硬编码正确写法 —— 否则修复被回退时测试照旧全过,成了假通过。
反向验证(故意把修复回退):
- 正常版本 19/19
- 回退版本 12/19,B1/B2/B4/B6/B9 全部 FAIL,能准确定位
⚠️ 第一版 harness 的 countOf 返回固定值,导致 B3/B8("传着传着到上限")
假通过。改成动态读递增计数后才是真验证。
回归:全新安装 33/33、附件 22/22、老基线 25/25、约束检查 20/20。
|
2026-11-02 10:42:03 +08:00 |
|
laoyang
|
9701291b4e
|
修复:改了 app.js 却没升资源版本,浏览器一直用旧 JS (#4)
用户报「图片最多上传 0 个,请先移除已上传的文件」——JS 弹的。
## 根因:不是逻辑没写对,是新代码根本没加载
#4 已经把拦截改成 `maxCount > 0 && countOf(kind) >= maxCount`,
但**忘了把 FB_ASSET_V 加 1**。页面仍然输出 app.js?v=13,
浏览器直接从缓存读旧的 app.js(旧的判断是 `countOf >= maxCount`,
maxCount=0 时 `0 >= 0` 为真,一个都不许传)。
## 修法
FB_ASSET_V 13 → 14。这一改同时覆盖 style.css 与 app.js,
用户下次刷新就会拉到新文件。
## 新增检查脚本
`_tooling/feedlite/fb_asset_version_check.py`:
对比「最后一次改 assets/ 的提交」与「最后一次改 FB_ASSET_V 的提交」,
前者更新就报错。改前端资源却忘升版本这类问题肉眼极难发现,
必须用规则卡住。
用脚本自查当前仓库,确认本提交修完后转为合规。
## 教训
改 assets/ 下任何文件 → **必须同时把 lib/bootstrap.php 的 FB_ASSET_V 加 1**,
这是硬配套动作,写在 README 里但这次没执行到位。
|
2026-11-02 09:10:15 +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
|
1434a6dfb6
|
修复:英文标识留空真能自动生成;表单必填前端就地提示不刷新 (#1)
## 问题 1:文案说「留空自动生成」,实际却报错
界面上英文标识写的是「留空自动生成」,且不带红星;但纯中文名
(如「同步日历」)经 slugify 过滤后是空串,服务端照旧报「请填写英文标识」。
文案与行为自相矛盾,用户不明所以。
根因:fb_sw_slugify() 只保留 ASCII,中文全被过滤掉。
修法(不靠加红星了事):给 slugify 加兜底前缀 + 序号
- 软件:中文名 → sw<序号>(如 sw2)
- 分类:中文名 → cat<序号>
- 编辑时留空 = 保持原标识,不因改名把已有网址打散
- 纯英文名行为不变(FeedLite → feedlite)
实测:同步日历 → sw1 / 我的软件 → sw2 / FeedLite → feedlite
## 问题 2:必填项没填,整页刷新才看到错误
原先完全靠服务端校验:提交 → POST → 整页重刷 → 页面顶部弹一条错误。
用户视角就是「什么都没填,页面闪了一下」。
新增 bindInlineValidate()(assets/app.js):
- 给 form 加 data-validate 即启用,零配置
- 就地校验:字段下方显示红框 + 原因,聚焦并滚到可见
- 用户一改动立刻撤掉提示,不一直红着
- 规则取自原生 required / minlength / pattern / type=email
🔴 关键实现细节:用**捕获阶段**监听,并调 stopImmediatePropagation()。
因为 bindNoDoubleSubmit 会在 submit 时禁用按钮 5 秒,若校验排在它之后,
被拦下的表单按钮会被锁住,用户以为点坏了。所以校验必须先跑且拦住它。
## 服务端仍保留兜底
JS 被绕过(禁用脚本、直接构造请求)时,后端照样拒绝空用户名、短口令。
## 顺带收敛资源版本
app.js?v=5 与 style.css?v=N 原先各自写死,已统一到 FB_ASSET_V(升到 11)。
加上前一轮的 layout.php / install.php,现在全站只有 bootstrap.php 一处定义。
验证:
- 标识生成与前端校验专项 21/21
- 全新安装 33/33、老基线 25/25、附件专项 22/22
- 安装向导 60/60、后台管理 30/30
|
2026-11-01 15:21:27 +08:00 |
|
laoyang
|
7a578fac47
|
静态资源版本收敛到 FB_ASSET_V 常量 (#1)
原先 layout.php 与 install.php 各写一份 style.css?v=N,改样式时只改一处,
另一处会漂移 —— 实际就发生了:首页 v=10、安装页还是 v=7,两页样式不一致。
现在统一在 lib/bootstrap.php 定义 FB_ASSET_V,两处都引用它。
以后改 assets 下的 css/js,只改这一个数字。
|
2026-11-01 12:47:41 +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
|
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 |
|