Files
lsky-pro-docker/src/app/Services/UserService.php
T
mole404 c242bfb12c fix(tags): 代码审查发现的 6 条问题全修 + 补真机/静态断言
审查(含新加的打标部分)发现并修掉:
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 个文件)重新生成。
2026-10-02 14:59:40 +08:00

53 lines
1.9 KiB
PHP

<?php
namespace App\Services;
use App\Models\Image;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class UserService
{
/**
* 删除图片以及物理文件,返回已删除数量
* 考虑到删除磁盘文件、服务器内部请求第三方删除接口比较消耗资源,传入的主键不应过多
* TODO 改进循环中更新数据
*
* @param array $keys
* @param User|null $user 传入用户数据则会根据用户id过滤
* @param string $field
* @return int
*/
public function deleteImages(array $keys, ?User $user = null, string $field = 'id'): int
{
$count = 0;
$model = Image::with('user', 'strategy', 'album')->when(! is_null($user), function (Builder $builder) use ($user) {
$builder->where('user_id', $user->id);
})->whereIn($field, $keys);
DB::transaction(function () use ($model, $keys, &$count) {
/** @var Image $image */
foreach ($model->cursor() as $image) {
// 相册图片数量更新
$image->album?->decrement('image_num');
// fork:图片删了,它与标签的关联行也要一并清掉。
// 迁移注释声明「关联行由应用代码显式删除」,这里就是那条规则(SQLite 未启用
// 外键级联,不显式删就会留下指向已删图片的残留行,无声累积)。
$image->tags()->detach();
// 更新相册图片数量
$image->delete();
// 更新数量
if ($image->user) {
$image->user->image_num = $image->user->images()->count();
$image->user->save();
}
$count++;
}
});
return $count;
}
}