审查(含新加的打标部分)发现并修掉:
1. 改名/删除标签会清空选中 ✗ —— refreshAfterTagChange() 走 setTags() → resetImages(),
会清空图片墙并 ds.clearSelection(),把用户正在打标的这批图连同弹窗勾选态一起丢掉
(老师问过的「编辑/删除会整页刷新」)。改为 patchCardsTag() 就地同步(卡片角标 + data-json
+ 详情卡 chip);只有「被删的标签正用作筛选项」这种结果集真的变了的情况才调 setTags()。
2. 删图片/删用户不清 image_tag / tags ✗ —— 两个迁移的注释都写着「关联行由应用代码显式删除」,
图片这条路径没落实,残留行会无声累积。UserService::deleteImages() 补 detach();
后台删用户时一并清标签与关联行。(本机 SQLite 未启用 PRAGMA foreign_keys,不能指望级联。)
附带 tools/check-tag-integrity.php:只读自检残留行,退出码非 0 即为异常。
3. 标签名进 data-json(单引号属性)未转义 ✗ —— 名字里带 ' 就能提前闭合属性(自伤型 XSS 面)。
三处注入(列表渲染 / 弹窗行 / syncCardTags)统一先 escapeHtml 再写。
4. 弹窗里几个 axios 调用没有 catch ✗ —— 网络错误或 422(名字超 64 字)会静默无反应。
统一走 apiErrMsg() 取后端 message;三个标签名输入框补 maxlength=64。
5. 后端 trim() 在校验之后 ✗ —— 只输入空格会建出空名标签。TagRequest 加 prepareForValidation()。
6. 两处硬编码路径 ✗(action="/user/tags/__id__"、axios.delete('/user/tags/'+id))→ 改用 route()。
测试:
- jsdom 118/118(新增 7 条:改名的局部同步、data-json 转义、maxlength+catch、
deleteImages/删用户的清理、TagRequest trim;测试台补 renderTagRoutes 渲染带 id 的路由)。
- 真机 70/70(新增 #2c:改标签名后选中不丢 / 图片墙不重载 / 角标就地换名,
6 项断言,在整轮末尾单开 context 跑,避免本机 1vCPU/965MB 下的页面 OOM)。
- 真机实证:图片挂 1 条关联 → deleteImages → 残留 0;check-tag-integrity.php 干净 ✓。
- tools/diff-vs-upstream.sh:已知偏离清单补登 UserService.php / Admin\UserController.php,
校验通过(103 项,恰好等于实际偏差)。
- patches/ios-longpress.patch 按原范围(3 个文件)重新生成。
40 lines
1.8 KiB
PHP
40 lines
1.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* fork 新增:标签数据完整性自检(只读,不改任何数据)。
|
|
*
|
|
* 迁移里声明「tags / image_tag 的关联行由应用代码显式删除」(本仓 SQLite 未启用
|
|
* PRAGMA foreign_keys,级联删除不可靠)。删图片走 UserService::deleteImages()、
|
|
* 删用户走 Admin\UserController,两处都必须照做 —— 这个脚本用来盯住这条规则。
|
|
*
|
|
* 用法(容器内):php tools/check-tag-integrity.php [sqlite 路径]
|
|
* 退出码:0 = 干净,1 = 有残留行。
|
|
*/
|
|
|
|
$path = $argv[1] ?? (getenv('DB_DATABASE') ?: __DIR__.'/../database/database.sqlite');
|
|
if (! is_file($path)) {
|
|
fwrite(STDERR, "找不到数据库文件:{$path}\n");
|
|
exit(2);
|
|
}
|
|
|
|
$db = new PDO('sqlite:'.$path);
|
|
$count = fn (string $sql): int => (int) $db->query($sql)->fetchColumn();
|
|
|
|
$total = $count('select count(*) from image_tag');
|
|
$orphanImage = $count('select count(*) from image_tag it left join images i on i.id = it.image_id where i.id is null');
|
|
$orphanTag = $count('select count(*) from image_tag it left join tags t on t.id = it.tag_id where t.id is null');
|
|
$orphanOwner = $count('select count(*) from tags t left join users u on u.id = t.user_id where u.id is null');
|
|
|
|
$line = fn (string $label, int $n): string => sprintf("%-22s %d %s\n", $label, $n, $n === 0 ? '✓' : '✗');
|
|
|
|
echo "标签数据完整性自检:{$path}\n";
|
|
echo sprintf("%-22s %d\n", 'image_tag 总行数', $total);
|
|
echo $line('指向已删图片的残留', $orphanImage);
|
|
echo $line('指向已删标签的残留', $orphanTag);
|
|
echo $line('属于已删用户的标签', $orphanOwner);
|
|
|
|
$bad = $orphanImage + $orphanTag + $orphanOwner;
|
|
echo $bad === 0 ? "结论:干净 ✓\n" : "结论:有 {$bad} 条残留,删图片/删用户的清理路径漏了 ✗\n";
|
|
|
|
exit($bad === 0 ? 0 : 1);
|