Files
lgp-admin-plus-api/app/Console/Commands/MenuSyncCommand.php
LQ bcf54c2727 初始化
缺陷:主题配色需要优化整体的同风格
2026-08-14 23:21:21 +08:00

158 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Console\Commands;
use App\Models\MenuModel;
use App\Models\RoleMenuRelationModel;
use App\Service\common\RedisService;
use Illuminate\Console\Command;
/**
* 按 config/lgp_menu.php 幂等同步 nl_menu
*
* name 是全局唯一键,同步以它为准:存在则按声明纠正字段,不存在则插入。
* pid 用父节点的 name 反查,所以配置里不出现任何自增 id反复执行结果一致。
* 同步完必须清菜单缓存,否则前端还会拿到 Redis 里的旧树。
*/
class MenuSyncCommand extends Command
{
protected $signature = 'lgp:menu-sync
{--dry-run : 只预演不写库}
{--grant-super : 同步后把新菜单补进超级管理员之外的角色(默认不动角色关系)}';
protected $description = '同步 LGP 业务菜单到 nl_menu幂等';
private bool $dryRun = false;
private int $created = 0;
private int $updated = 0;
public function handle(): int
{
$this->dryRun = (bool) $this->option('dry-run');
if ($this->dryRun) {
$this->warn('预演模式:不会写入任何数据');
}
$tree = config('lgp_menu', []);
if (empty($tree)) {
$this->error('config/lgp_menu.php 为空');
return self::FAILURE;
}
$this->walk($tree, 0);
$this->newLine();
$this->info("新增 {$this->created} 条,更新 {$this->updated}");
if (!$this->dryRun) {
// 菜单树按角色缓存在 Redis不清的话前端看不到新菜单
RedisService::getInstance()->init(config('nl.redis.menu_key'))->delAll();
$this->info('已清空菜单缓存');
$this->reportRoleBinding();
}
return self::SUCCESS;
}
/**
* @param array<int, array<string, mixed>> $nodes
*/
private function walk(array $nodes, int $pid): void
{
foreach ($nodes as $node) {
// parent 显式声明时优先(用于挂到脚手架已有菜单下),否则跟随递归层级
$parentId = isset($node['parent'])
? $this->resolveParentId((string) $node['parent'])
: $pid;
$id = $this->upsert($node, $parentId);
if (!empty($node['children'])) {
$this->walk($node['children'], $id);
}
}
}
private function resolveParentId(string $name): int
{
$id = (int) MenuModel::where('name', $name)->where('deleted_at', 0)->value('id');
if ($id === 0) {
$this->warn("父级菜单 {$name} 不存在,该节点挂到根目录");
}
return $id;
}
/**
* @param array<string, mixed> $node
* @return int 菜单 id预演模式下返回 0子节点会挂到根仅影响预演输出
*/
private function upsert(array $node, int $pid): int
{
$isDirectory = ($node['component'] ?? '') === 'BasicLayout';
$attributes = [
'title' => (string) $node['title'],
'icon' => (string) ($node['icon'] ?? ''),
'path' => (string) $node['path'],
'component' => (string) $node['component'],
'redirect' => (string) ($node['redirect'] ?? ''),
// 取反字段0 开启缓存 / 0 固定标签
'keep_alive' => (int) ($node['keep_alive'] ?? ($isDirectory ? 0 : 1)),
'hide_in_menu' => (int) ($node['hide_in_menu'] ?? 0),
'affix_tab' => (int) ($node['affix_tab'] ?? 1),
'badge' => (string) ($node['badge'] ?? ''),
'badge_type' => (int) ($node['badge_type'] ?? 0),
'badge_variants' => (int) ($node['badge_variants'] ?? 0),
'iframe_src' => (string) ($node['iframe_src'] ?? ''),
'query' => (string) ($node['query'] ?? ''),
'pid' => $pid,
'sort' => (int) ($node['sort'] ?? 0),
];
$name = (string) $node['name'];
$exists = MenuModel::where('name', $name)->first();
if ($exists) {
$diff = array_filter(
$attributes,
fn ($value, $key) => (string) $exists->{$key} !== (string) $value,
ARRAY_FILTER_USE_BOTH
);
// 已软删的菜单同步时一并复活,否则改配置也救不回来
if ((int) $exists->deleted_at !== 0) {
$diff['deleted_at'] = 0;
}
if (empty($diff)) {
$this->line(" = {$attributes['title']} ({$name})");
return (int) $exists->id;
}
$this->line(" ~ {$attributes['title']} ({$name}) " . implode(', ', array_keys($diff)));
$this->updated++;
if (!$this->dryRun) {
$diff['updated_at'] = time();
MenuModel::where('id', $exists->id)->update($diff);
}
return (int) $exists->id;
}
$this->line(" + {$attributes['title']} ({$name})");
$this->created++;
if ($this->dryRun) {
return 0;
}
$attributes['created_at'] = time();
return (int) MenuModel::insertGetId($attributes);
}
/**
* 只提示不自动授权:给谁看哪些菜单是业务决策,命令替用户决定容易造成越权
*/
private function reportRoleBinding(): void
{
if (!$this->option('grant-super')) {
$bound = RoleMenuRelationModel::distinct()->count('role_id');
$this->newLine();
$this->warn("超级管理员role_id=1自动可见全部菜单其余 {$bound} 个已授权角色需到「角色管理」重新勾选新菜单。");
}
}
}