output = $output; } /** * 把 adminInitData/SystemMenu.php 快照对齐到当前库. * * @param string $snapshotName 快照文件名(adminInitData/ 目录下,不含扩展名) * * @return array{inserted: int, aligned: int, skipped_exists: int, skipped_user: int, warnings: array} */ public function align(string $snapshotName = 'SystemMenu'): array { $snapshot = include __DIR__ . '/adminInitData/' . $snapshotName . '.php'; // 框架合法 pid 集合:快照内全部 id + 顶级 0 + 欢迎页虚拟 pid $frameworkPids = array_merge(array_column($snapshot, 'id'), [0, self::HOME_PID]); $stats = [ 'inserted' => 0, 'aligned' => 0, 'skipped_exists' => 0, 'skipped_user' => 0, 'warnings' => [], ]; $now = time(); foreach ($snapshot as $row) { // 注意:每次操作都取全新 Query 对象,复用同一对象会残留 where 条件导致误判。 // 原生查询不过滤软删除:软删行仍占用主键,必须先识别,避免 INSERT 冲突或复活。 $exists = Db::name('system_menu')->where('id', $row['id'])->find(); if (empty($exists)) { $data = $row; $data['create_time'] = $now; $data['update_time'] = $now; $data['delete_time'] = 0; Db::name('system_menu')->insert($data); ++$stats['inserted']; $this->writeln(" - 新增菜单 id={$row['id']} {$row['title']} (pid={$row['pid']})"); continue; } // 用户主动软删除(隐藏)的框架菜单:不复活 if ((int) ($exists['delete_time'] ?? 0) !== 0) { ++$stats['skipped_user']; $this->writeln(" - 菜单 id={$row['id']} {$row['title']} 已被软删除(用户隐藏),跳过"); continue; } // id 被非快照行占用:href 不一致(分组行 href 均为空串,仅叶子行可比对) $snapshotHref = $row['href'] ?? ''; if ($snapshotHref !== '' && ($exists['href'] ?? '') !== $snapshotHref) { ++$stats['skipped_user']; $stats['warnings'][] = "菜单 id={$row['id']} 已被其他记录占用(href={$exists['href']}),快照行 {$snapshotHref} 未插入,请人工核对"; continue; } // 用户已把该行挪进自建分组:不视为框架结构,跳过 if (!in_array((int) $exists['pid'], $frameworkPids, true)) { ++$stats['skipped_user']; $this->writeln(" - 菜单 id={$row['id']} {$row['title']} 已挂用户分组(pid={$exists['pid']}),跳过"); continue; } // 结构对齐:仅 pid + sort,不动文案与状态 if ((int) $exists['pid'] !== (int) $row['pid'] || (int) $exists['sort'] !== (int) $row['sort']) { Db::name('system_menu')->where('id', $row['id'])->update([ 'pid' => $row['pid'], 'sort' => $row['sort'], 'update_time' => $now, ]); ++$stats['aligned']; $this->writeln(" - 对齐菜单 id={$row['id']} {$row['title']} (pid {$exists['pid']}->{$row['pid']}, sort {$exists['sort']}->{$row['sort']})"); continue; } ++$stats['skipped_exists']; } return $stats; } protected function writeln(string $message): void { if ($this->output !== null) { $this->output->writeln($message); } } }