首页改介绍页;列表只服务当前软件;新增UUID追踪让提交人看得到未审核反馈
用户提的两个核心问题:
一、首页不该列全部问题
场景:这套系统是给某个软件配反馈入口、把地址贴进那软件的「意见反馈」里。
访客总是从某个软件过来的,看别的软件的问题对他没意义。
⇒ / 不带 sw 时渲染介绍页(特点介绍 + 「点右上角设置按钮选软件」+ 软件卡片)
带 sw 才是该软件的问题列表,检索/筛选/分类全在本软件范围内
二、提交后未审核,用户点不进去(真实缺陷)
邮件里给了「点击查看您刚才提交的反馈」,但 thread.php 只在 visible=1 时
才给看,未审核的一律 404 / 只看到「等待审核」静态页 —— 链接等于白给。
⇒ threads 加 track_token(32位hex = 128位熵),提交时生成:
- 邮件里所有链接一律带 ?t=<token>(不再是裸 id)
- 带对 token:可看完整详情、可回复,哪怕未公开/已关闭
- 没 token 或 token 错:对外一律 404,不泄露存在性
- 公开列表仍用 id 链接(公开内容谁都能看,用于避免重复提交)
实现细节:
- threads.track_token + 唯一索引;幂等迁移,存量数据自动回填
- fb_track_new_token / fb_track_token_ok(hash_equals 防时序探测)
/ fb_thread_viewable / fb_thread_link(thread, personal)
- thread.php 权限从 fb_thread_public 改为 fb_thread_viewable
- 带 token 看自己未公开的反馈时,顶部给「私人跟踪链接」提示条
- 回复表单与回复后跳转都保留 token,否则回复完就失去访问权
- 提交完成后直接带 token 跳详情页,不再只给静态等待页
- close 类邮件补上「查看这条反馈」按钮(原来完全没链接)
- 顶栏「全部问题」改为「问题列表」且只在选中软件后显示
- 详情/提交页「返回问题列表」按所属软件返回,不再回介绍页
- new.php 无 sw 时不再默认落到第一个软件,改为让用户先选
(默默替选会让反馈进错软件、通知发错人)
验证:首页 0 条问题卡片且未泄露任何标题、三个软件入口齐全;
未公开#4 无 token 404 / 错 token 404 / 正确 token 200 且显示完整详情
(不再卡在「等待审核」)、提示条与表单 token 都在;
已公开#2 无 token 可看;5 类邮件(回执/通过/回复/状态/关闭)链接全带 token;
两套回归各 30+ 项断言全过。
This commit is contained in:
+77
-1
@@ -447,7 +447,11 @@ function fb_color_line($hex) {
|
||||
/** #rrggbb -> [r,g,b],非法返回 null */
|
||||
function fb_hex_to_rgb($hex) {
|
||||
$h = ltrim((string)$hex, '#');
|
||||
if (strlen($h) === 3) { $h = $h[0].$h[0].$h[1].$h[1].$h[2].$h[2]; }
|
||||
// 三位简写只接受纯十六进制字符(#abc -> #aabbcc);
|
||||
// 先做字符校验再展开,否则 "bad" 这种非色值会被当成 #bbaadd
|
||||
if (strlen($h) === 3 && preg_match('/^[0-9a-fA-F]{3}$/', $h)) {
|
||||
$h = $h[0].$h[0].$h[1].$h[1].$h[2].$h[2];
|
||||
}
|
||||
if (!preg_match('/^[0-9a-fA-F]{6}$/', $h)) { return null; }
|
||||
return [hexdec(substr($h, 0, 2)), hexdec(substr($h, 2, 2)), hexdec(substr($h, 4, 2))];
|
||||
}
|
||||
@@ -628,6 +632,78 @@ function fb_thread_public(array $thread) {
|
||||
return fb_is_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个新的追踪 token(提交反馈时调用)。
|
||||
* 32 位 hex = 128 位熵,猜不出来;这个值只存在自己库里、只发给提问人邮箱。
|
||||
*/
|
||||
function fb_track_new_token() {
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
/** 这个请求带来的 token 是否与帖子的匹配(常数时间比较,防时序探测) */
|
||||
function fb_track_token_ok(array $thread, $given) {
|
||||
$given = trim((string)$given);
|
||||
if ($given === '') { return false; }
|
||||
$real = isset($thread['track_token']) ? trim((string)$thread['track_token']) : '';
|
||||
if ($real === '') { return false; }
|
||||
return hash_equals($real, $given);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前访问者是否「有权跟踪」这条帖子 —— 即带对了 token。
|
||||
* 审核后台的管理员也算有权(不然审核时看不了)。
|
||||
*/
|
||||
function fb_thread_trackable(array $thread, $given = null) {
|
||||
if ($given === null) { $given = isset($_GET['t']) ? (string)$_GET['t'] : ''; }
|
||||
if (fb_track_token_ok($thread, $given)) { return true; }
|
||||
return fb_is_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前访问者能否【查看】这条帖子。
|
||||
*
|
||||
* 公开的(visible=1)所有人可看;
|
||||
* 未公开的只有两种人能看:审核后台管理员、拿着正确 token 的提交人。
|
||||
* 这就是「不登录也能跟踪自己那条」的实现方式,不靠任何登录态。
|
||||
*/
|
||||
function fb_thread_viewable(array $thread, $given = null) {
|
||||
if (fb_thread_public($thread)) { return true; }
|
||||
return fb_thread_trackable($thread, $given);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回「某个软件的问题列表」的链接。
|
||||
*
|
||||
* 首页现在是介绍页,不带 sw 的回首页等于把用户扔回介绍页 ——
|
||||
* 从详情/提交页点「返回问题列表」应该回到**当前软件**的列表。
|
||||
*/
|
||||
function fb_software_list_url($sw) {
|
||||
if (!is_array($sw) || empty($sw['slug'])) { return 'index.php'; }
|
||||
return 'index.php?sw=' . urlencode((string)$sw['slug']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这条帖子的对外链接。
|
||||
*
|
||||
* 🔴 用 token 不用 id:
|
||||
* - 邮件里发给提问人的链接一律带 ?t=<token>,他才能看未公开的那条;
|
||||
* - 公开列表里给所有人看的链接照旧用 id(公开内容本来就谁都能看)。
|
||||
* 早期所有链接都是 thread.php?id=N,导致「未审核就点不进去」——
|
||||
* id 是自增的,用它当权限等于没有权限。
|
||||
*
|
||||
* @param array $thread 帖子行
|
||||
* @param bool $personal true = 带 token 的私人链接;false = 公开链接
|
||||
*/
|
||||
function fb_thread_link(array $thread, $personal = false) {
|
||||
$id = (int)(isset($thread['id']) ? $thread['id'] : 0);
|
||||
$url = fb_base_url() . '/thread.php?id=' . $id;
|
||||
if ($personal) {
|
||||
$tok = isset($thread['track_token']) ? trim((string)$thread['track_token']) : '';
|
||||
if ($tok !== '') { $url .= '&t=' . rawurlencode($tok); }
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/* ---------------- 安装状态 ---------------- */
|
||||
|
||||
/** FeedLite 开源仓库地址(页脚版权链接的默认值,可在后台改) */
|
||||
|
||||
Reference in New Issue
Block a user