tableHasColumn('sa_system_role', 'dept_id')) { return ['dept_id' => $deptId, 'copied' => 0, 'skipped' => 0, 'pruned' => 0, 'message' => 'dept_id column missing']; } $templates = $this->defaultTemplateRoles(); if (empty($templates)) { return ['dept_id' => $deptId, 'copied' => 0, 'skipped' => 0, 'pruned' => 0, 'message' => 'no template roles']; } $copied = 0; $skipped = 0; foreach ($templates as $template) { $template = (array) $template; $templateId = (int) ($template['id'] ?? 0); $code = (string) ($template['code'] ?? ''); if ($templateId <= 0 || $code === '') { continue; } if ($this->roleExists($deptId, $code)) { $skipped++; continue; } $newId = $this->insertRoleFromTemplate($template, $deptId); if ($newId > 0) { $this->copyRoleMenus($templateId, $newId); $copied++; } } $pruned = 0; if ($pruneExtra) { $pruned = $this->pruneExtraChannelRoles($deptId); } return ['dept_id' => $deptId, 'copied' => $copied, 'skipped' => $skipped, 'pruned' => $pruned]; } /** * 为所有已启用渠道补齐三个默认角色,并移除多余历史角色 */ public function syncAllChannelsFromDefault(): array { $deptIds = SystemDept::where('status', 1)->where('id', '>', 0)->column('id'); $result = []; foreach ($deptIds as $deptId) { $result[(int) $deptId] = $this->copyDefaultRolesToDept((int) $deptId, true); } return $result; } /** * 删除渠道下全部角色及菜单关联 */ public function deleteRolesByDept(int $deptId): int { if ($deptId <= 0) { return 0; } $roleIds = SystemRole::where('dept_id', $deptId)->column('id'); if (empty($roleIds)) { return 0; } Db::name('sa_system_user_role')->whereIn('role_id', $roleIds)->delete(); Db::name('sa_system_role_menu')->whereIn('role_id', $roleIds)->delete(); Db::name('sa_system_role_dept')->whereIn('role_id', $roleIds)->delete(); return SystemRole::destroy($roleIds); } /** * 将用户已绑定的模板角色映射到其渠道对应角色(仅三个默认 code) */ public function remapUserRolesToChannelRoles(): int { if (!$this->tableHasColumn('sa_system_role', 'dept_id')) { return 0; } $codes = $this->getDefaultChannelRoleCodes(); if (empty($codes)) { return 0; } $codeList = "'" . implode("','", array_map('addslashes', $codes)) . "'"; return Db::execute( 'UPDATE `sa_system_user_role` ur INNER JOIN `sa_system_user` u ON ur.user_id = u.id INNER JOIN `sa_system_role` r_old ON ur.role_id = r_old.id INNER JOIN `sa_system_role` r_new ON r_new.dept_id = u.dept_id AND r_new.code = r_old.code SET ur.role_id = r_new.id WHERE u.dept_id > 0 AND r_old.dept_id = 0 AND r_old.code IN (' . $codeList . ') AND r_old.id <> ' . self::SUPER_ADMIN_ROLE_ID ); } /** * @return string[] */ public function getDefaultChannelRoleCodes(): array { $codes = config('plugin.saiadmin.saithink.channel_default_role_codes', []); if (!is_array($codes) || $codes === []) { return ['yijidaili', 'erjidaili', 'sanjidaili']; } $out = []; foreach ($codes as $code) { $code = trim((string) $code); if ($code !== '') { $out[] = $code; } } return $out; } /** * 删除渠道下不在默认三个 code 内、且未被用户绑定的角色 */ public function pruneExtraChannelRoles(int $deptId): int { if ($deptId <= 0) { return 0; } $allowed = $this->getDefaultChannelRoleCodes(); if (empty($allowed)) { return 0; } $query = SystemRole::where('dept_id', $deptId)->whereNotIn('code', $allowed); $roleIds = $query->column('id'); if (empty($roleIds)) { return 0; } $usedIds = Db::name('sa_system_user_role')->whereIn('role_id', $roleIds)->column('role_id'); $usedMap = array_flip($usedIds ?: []); $pruned = 0; foreach ($roleIds as $roleId) { if (isset($usedMap[$roleId])) { continue; } Db::name('sa_system_role_menu')->where('role_id', $roleId)->delete(); Db::name('sa_system_role_dept')->where('role_id', $roleId)->delete(); SystemRole::destroy($roleId); $pruned++; } return $pruned; } /** * @return array> */ private function defaultTemplateRoles(): array { $codes = $this->getDefaultChannelRoleCodes(); if (empty($codes)) { return []; } $query = Db::table('sa_system_role') ->where('id', '<>', self::SUPER_ADMIN_ROLE_ID) ->whereIn('code', $codes); if ($this->tableHasColumn('sa_system_role', 'dept_id')) { $query->where('dept_id', 0); } $rows = $query->select()->toArray(); if (empty($rows)) { return []; } if (count($rows) === count($codes)) { return $this->sortRolesByLevelDesc($rows); } // 按配置 code 补齐,缺失的 code 跳过,最终按角色级别从大到小排序 $byCode = []; foreach ($rows as $row) { $byCode[(string) ($row['code'] ?? '')] = $row; } $ordered = []; foreach ($codes as $code) { if (isset($byCode[$code])) { $ordered[] = $byCode[$code]; } } return $this->sortRolesByLevelDesc($ordered); } /** * 按角色级别从大到小排序(同级别按 sort 降序) * * @param array> $rows * @return array> */ private function sortRolesByLevelDesc(array $rows): array { usort($rows, static function (array $a, array $b): int { $levelCompare = (int) ($b['level'] ?? 0) <=> (int) ($a['level'] ?? 0); if ($levelCompare !== 0) { return $levelCompare; } return (int) ($b['sort'] ?? 0) <=> (int) ($a['sort'] ?? 0); }); return $rows; } private function insertRoleFromTemplate(array $template, int $deptId): int { unset( $template['id'], $template['create_time'], $template['update_time'], $template['delete_time'] ); $template['dept_id'] = $deptId; $now = date('Y-m-d H:i:s'); if (!isset($template['create_time'])) { $template['create_time'] = $now; } if (!isset($template['update_time'])) { $template['update_time'] = $now; } return (int) Db::table('sa_system_role')->insertGetId($template); } private function copyRoleMenus(int $fromRoleId, int $toRoleId): void { $menuIds = Db::name('sa_system_role_menu')->where('role_id', $fromRoleId)->column('menu_id'); if (empty($menuIds)) { return; } $rows = []; foreach ($menuIds as $menuId) { $rows[] = ['role_id' => $toRoleId, 'menu_id' => $menuId]; } Db::name('sa_system_role_menu')->limit(100)->insertAll($rows); } private function roleExists(int $deptId, string $code): bool { return SystemRole::where('dept_id', $deptId)->where('code', $code)->count() > 0; } private function tableHasColumn(string $table, string $column): bool { try { $fields = Db::getFields($table); return isset($fields[$column]); } catch (\Throwable $e) { return false; } } }