280 lines
8.9 KiB
PHP
280 lines
8.9 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace plugin\saiadmin\app\service;
|
||
|
||
use plugin\saiadmin\app\model\system\SystemDept;
|
||
use plugin\saiadmin\app\model\system\SystemRole;
|
||
use plugin\saiadmin\exception\ApiException;
|
||
use support\think\Db;
|
||
|
||
/**
|
||
* 渠道角色:从默认模板复制指定角色、同步与删除
|
||
*/
|
||
class SystemRoleChannelService
|
||
{
|
||
/** 全局超级管理员角色,不参与渠道复制 */
|
||
public const SUPER_ADMIN_ROLE_ID = 1;
|
||
|
||
/**
|
||
* 为渠道从默认模板复制三个代理角色(缺失则补齐,不整包跳过)
|
||
*/
|
||
public function copyDefaultRolesToDept(int $deptId, bool $pruneExtra = false): array
|
||
{
|
||
if ($deptId <= 0) {
|
||
throw new ApiException('Invalid channel id');
|
||
}
|
||
if (!$this->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<int, array<string, mixed>>
|
||
*/
|
||
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<int, array<string, mixed>> $rows
|
||
* @return array<int, array<string, mixed>>
|
||
*/
|
||
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;
|
||
}
|
||
}
|
||
}
|