diff --git a/server/app/adminapi/controller/DesktopController.php b/server/app/adminapi/controller/DesktopController.php index 3c2b84333..caf3191d4 100644 --- a/server/app/adminapi/controller/DesktopController.php +++ b/server/app/adminapi/controller/DesktopController.php @@ -28,7 +28,10 @@ class DesktopController extends BaseAdminController if (empty($identity) || empty($identity['admin_id'])) { return $this->fail('登录已失效,请重新登录'); } - if ((int)($identity['terminal'] ?? 0) !== AdminTerminalEnum::WECOM_RPA) { + // 桌面软件(7)和客服后台代登的手机控制台(8)都用这个接口换身份; + // 返回真实终端,客服后台据此区分两种会话,桌面令牌不能冒充手机令牌。 + $terminal = (int)($identity['terminal'] ?? 0); + if (!in_array($terminal, AdminTerminalEnum::wecomRpaTerminals(), true)) { return $this->fail('当前登录令牌不属于企业微信客服桌面端'); } @@ -59,7 +62,7 @@ class DesktopController extends BaseAdminController 'nickname' => $name !== '' ? $name : $account, 'mobile' => '', 'avatar' => (string)$admin['avatar'], - 'terminal' => AdminTerminalEnum::WECOM_RPA, + 'terminal' => $terminal, 'expire_time' => (int)($identity['expire_time'] ?? 0), 'status' => 'active', 'account_type' => 'admin', diff --git a/server/app/adminapi/validate/LoginValidate.php b/server/app/adminapi/validate/LoginValidate.php index a1d18d624..734cc39a9 100755 --- a/server/app/adminapi/validate/LoginValidate.php +++ b/server/app/adminapi/validate/LoginValidate.php @@ -33,7 +33,7 @@ class LoginValidate extends BaseValidate { protected $rule = [ 'terminal' => 'require|in:' . AdminTerminalEnum::PC . ',' . AdminTerminalEnum::MOBILE - . ',' . AdminTerminalEnum::WECOM_RPA, + . ',' . AdminTerminalEnum::WECOM_RPA . ',' . AdminTerminalEnum::WECOM_RPA_MOBILE, 'account' => 'require', 'password' => 'require|password', ]; diff --git a/server/app/common/enum/AdminTerminalEnum.php b/server/app/common/enum/AdminTerminalEnum.php index 63585a9dd..ddd01aa1f 100755 --- a/server/app/common/enum/AdminTerminalEnum.php +++ b/server/app/common/enum/AdminTerminalEnum.php @@ -27,4 +27,15 @@ class AdminTerminalEnum const MOBILE = 2; // 企业微信客服桌面软件。与网页端分开保存会话,桌面登录不会顶掉浏览器登录。 const WECOM_RPA = 7; + // 企业微信客服手机远程控制台。由客服后台服务端代为登录,单独保存会话, + // 手机登录既不会顶掉桌面软件(7),也不会和网页后台(1)互踢。 + const WECOM_RPA_MOBILE = 8; + + /** + * 可以调用 /adminapi/desktop/session 换取客服身份的终端。 + */ + public static function wecomRpaTerminals(): array + { + return [self::WECOM_RPA, self::WECOM_RPA_MOBILE]; + } } diff --git a/server/server.zip b/server/server.zip deleted file mode 100644 index 5624f1d52..000000000 Binary files a/server/server.zip and /dev/null differ diff --git a/server/sql/1.9.20260928/add_wecom_rpa_remote_menu.sql b/server/sql/1.9.20260928/add_wecom_rpa_remote_menu.sql new file mode 100644 index 000000000..71f49e896 --- /dev/null +++ b/server/sql/1.9.20260928/add_wecom_rpa_remote_menu.sql @@ -0,0 +1,47 @@ +-- 企业微信客服助手:手机远程控制台(终端 8)使用的菜单权限。 +-- +-- 新增一个不在侧边栏显示的目录“企微客服助手”,下挂四个按钮权限。客服后台只读取 +-- /adminapi/desktop/session 返回的 permissions 做校验,ZYT 这边没有对应页面。 +-- +-- wecom_rpa.remote/control 远程启停本人电脑的监听(建议授予客服) +-- wecom_rpa.remote/all 查看并控制所有客服的电脑(仅管理员) +-- wecom_rpa.blacklist/add 把会话加入黑名单(建议授予客服) +-- wecom_rpa.blacklist/remove 把会话移出黑名单(主管 / 管理员) +-- +-- 默认不授予任何角色:先在“权限管理 → 角色管理”里给 1~2 位客服所在角色勾选试用, +-- 再逐步放开。登录手机控制台、查看本人电脑与会话不需要这些权限。 +-- 注意:拥有“全部菜单”的角色原先返回 '*',新增按钮后会改为返回明细权限(与以往 +-- 新增菜单的行为一致),需要时在角色里补勾这四项。表前缀如非 zyt_ 请调整。 + +START TRANSACTION; + +INSERT INTO `zyt_system_menu` + (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`) +SELECT + 0, 'M', '企微客服助手', 'el-icon-Iphone', 0, '', 'wecom_rpa', '', '', '', 0, 0, 0, + UNIX_TIMESTAMP(), UNIX_TIMESTAMP() +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM `zyt_system_menu` WHERE `type` = 'M' AND `paths` = 'wecom_rpa' +); + +SET @wecom_rpa_menu_id = ( + SELECT `id` FROM `zyt_system_menu` + WHERE `type` = 'M' AND `paths` = 'wecom_rpa' + ORDER BY `id` ASC LIMIT 1 +); + +INSERT INTO `zyt_system_menu` + (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`) +SELECT @wecom_rpa_menu_id, 'A', item.`name`, '', item.`sort`, item.`perms`, '', '', '', '', 0, 1, 0, + UNIX_TIMESTAMP(), UNIX_TIMESTAMP() +FROM ( + SELECT '远程启停本人电脑' AS `name`, 40 AS `sort`, 'wecom_rpa.remote/control' AS `perms` + UNION ALL SELECT '控制所有客服电脑', 30, 'wecom_rpa.remote/all' + UNION ALL SELECT '会话加入黑名单', 20, 'wecom_rpa.blacklist/add' + UNION ALL SELECT '会话移出黑名单', 10, 'wecom_rpa.blacklist/remove' +) AS item +WHERE @wecom_rpa_menu_id IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM `zyt_system_menu` AS existing WHERE existing.`perms` = item.`perms`); + +COMMIT; diff --git a/server/tests/AdminDesktopAuthContractTest.php b/server/tests/AdminDesktopAuthContractTest.php index 3b16d8641..2192a0bd4 100644 --- a/server/tests/AdminDesktopAuthContractTest.php +++ b/server/tests/AdminDesktopAuthContractTest.php @@ -15,6 +15,11 @@ function adminDesktopExpect(bool $condition, string $message): void } adminDesktopExpect(AdminTerminalEnum::WECOM_RPA === 7, 'desktop admin terminal remains 7'); +adminDesktopExpect(AdminTerminalEnum::WECOM_RPA_MOBILE === 8, 'mobile console terminal is 8'); +adminDesktopExpect( + AdminTerminalEnum::wecomRpaTerminals() === [7, 8], + 'only the desktop and mobile console terminals may read the desktop session' +); $validator = file_get_contents(dirname(__DIR__) . '/app/adminapi/validate/LoginValidate.php'); $controller = file_get_contents(dirname(__DIR__) . '/app/adminapi/controller/DesktopController.php'); @@ -23,6 +28,10 @@ adminDesktopExpect( str_contains($validator, 'AdminTerminalEnum::WECOM_RPA'), 'admin login accepts the dedicated desktop terminal' ); +adminDesktopExpect( + str_contains($validator, 'AdminTerminalEnum::WECOM_RPA_MOBILE'), + 'admin login accepts the dedicated mobile console terminal' +); adminDesktopExpect( str_contains($controller, "public array \$notNeedLogin = ['session'];") && str_contains($controller, "'user_id' => 'admin:'") diff --git a/server/tests/AdminDesktopSessionBehaviorTest.php b/server/tests/AdminDesktopSessionBehaviorTest.php index 9fe878c34..3333c1a26 100644 --- a/server/tests/AdminDesktopSessionBehaviorTest.php +++ b/server/tests/AdminDesktopSessionBehaviorTest.php @@ -90,8 +90,15 @@ namespace { desktopCheck($invoke($valid)['data']['root'] === true, 'root comes from current account'); Admin::$row['root'] = 0; + desktopCheck($result['data']['terminal'] === 7, 'desktop token reports the desktop terminal'); + $mobile = $invoke(array_replace($valid, ['terminal' => 8])); + desktopCheck($mobile['code'] === 1, 'mobile console token (terminal 8) is accepted'); + desktopCheck($mobile['data']['terminal'] === 8, 'mobile console token reports terminal 8'); + desktopCheck($mobile['data']['user_id'] === 'admin:9', 'mobile console maps to the same tenant identity'); + desktopCheck($invoke([])['code'] === 0, 'missing or expired cached identity rejected'); desktopCheck($invoke(array_replace($valid, ['terminal' => 1]))['code'] === 0, 'web token rejected'); + desktopCheck($invoke(array_replace($valid, ['terminal' => 2]))['code'] === 0, 'admin mobile web token rejected'); Admin::$row['disable'] = 1; desktopCheck($invoke($valid)['code'] === 0, 'disabled account rejected'); Admin::$row = [];