Compare commits

...
3 Commits
Author SHA1 Message Date
gr 3cdc812f2b ;rgb:0000/0000/0000
更新
2026-09-28 14:53:47 +08:00
gr 71304f356d i
Merge branch 'master' into 9-28
2026-09-28 14:20:46 +08:00
gr 694c83dc7d 更新 2026-09-28 14:18:12 +08:00
7 changed files with 86 additions and 3 deletions
@@ -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)和客服后台代登的手机控制台(9)都用这个接口换身份;
// 返回真实终端,客服后台据此区分两种会话,桌面令牌不能冒充手机令牌。
$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',
@@ -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',
];
@@ -27,4 +27,16 @@ class AdminTerminalEnum
const MOBILE = 2;
// 企业微信客服桌面软件。与网页端分开保存会话,桌面登录不会顶掉浏览器登录。
const WECOM_RPA = 7;
// 8 已被 AI 浏览器(app/mcp/service/ConsoleService::TERMINAL)占用,不要复用。
// 企业微信客服手机远程控制台。由客服后台服务端代为登录,单独保存会话,
// 手机登录既不会顶掉桌面软件(7),也不会和网页后台(1)、AI 浏览器(8)互踢。
const WECOM_RPA_MOBILE = 9;
/**
* 可以调用 /adminapi/desktop/session 换取客服身份的终端。
*/
public static function wecomRpaTerminals(): array
{
return [self::WECOM_RPA, self::WECOM_RPA_MOBILE];
}
}
BIN
View File
Binary file not shown.
@@ -0,0 +1,47 @@
-- 企业微信客服助手:手机远程控制台(终端 9)使用的菜单权限。
--
-- 新增一个不在侧边栏显示的目录“企微客服助手”,下挂四个按钮权限。客服后台只读取
-- /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;
@@ -15,6 +15,15 @@ function adminDesktopExpect(bool $condition, string $message): void
}
adminDesktopExpect(AdminTerminalEnum::WECOM_RPA === 7, 'desktop admin terminal remains 7');
adminDesktopExpect(AdminTerminalEnum::WECOM_RPA_MOBILE === 9, 'mobile console terminal is 9');
adminDesktopExpect(
AdminTerminalEnum::WECOM_RPA_MOBILE !== \app\mcp\service\ConsoleService::TERMINAL,
'mobile console must not share the AI browser console terminal'
);
adminDesktopExpect(
AdminTerminalEnum::wecomRpaTerminals() === [7, 9],
'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 +32,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:'")
@@ -90,8 +90,16 @@ 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' => 9]));
desktopCheck($mobile['code'] === 1, 'mobile console token (terminal 9) is accepted');
desktopCheck($mobile['data']['terminal'] === 9, 'mobile console token reports terminal 9');
desktopCheck($mobile['data']['user_id'] === 'admin:9', 'mobile console maps to the same tenant identity');
desktopCheck($invoke(array_replace($valid, ['terminal' => 8]))['code'] === 0, 'AI browser console token rejected');
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 = [];