fix: 打通登录门禁死锁,失败信息区分未连接/登录被拒/未通过验证

继续修复部署时暴露的门禁设计问题:

- 登录门禁死锁:/login、/register、简幻通的 /auth 本身就是登录流程的一部分,
  原先门禁把它们一并拦下,形成「要先登录才能发登录指令」,账号永远卡在未验证状态。
  新增 server.preLoginCommands 白名单(默认 login,l,register,reg,auth),
  这些前缀在未验证时也放行,其余指令仍要求已完成验证。

- 离线时指令被静默丢弃却回报「已发送」:bot.chat() 在未连接时无处可发。
  canSendCommand() 现在要求已连接并进入世界,失败信息区分
  「未连接」「登录被服务器拒绝」「未通过登录验证」三种情况并给出对应建议。

- 前端配置表单支持 list 类型字段(逗号分隔的字符串数组)。

测试从 252 增至 263,全部通过。
这个提交包含在:
lyizai
2026-09-27 22:11:52 +08:00
父节点 d6b1339867
当前提交 2671da95d5
共修改 10 个文件,包含 279 行新增和 18 行删除
+47 -1
查看文件
@@ -146,8 +146,24 @@ test('positions 输出各机器人坐标供大地图打点', () => {
assert.strictEqual(positions[0].stateName, '');
});
test('未完成登录验证时拒绝发送指令与聊天', () => {
test('未连接时拒绝发送指令与聊天,并说明是「未连接」', () => {
const { pool } = setup();
const command = pool.sendCommand('BotA', 'u claimmode');
assert.strictEqual(command.ok, false);
assert.ok(command.message.includes('未连接'), '离线时应明确说明未连接,而不是含糊的失败');
const chat = pool.sendChat('BotA', '大家好');
assert.strictEqual(chat.ok, false);
assert.ok(chat.message.includes('未连接'));
});
test('已连接但未完成登录验证时拒绝发送指令与聊天', () => {
const { pool } = setup();
const entry = pool.get('BotA');
entry.manager.bot = { chat: () => {} };
entry.manager.state = 'online';
entry.manager.loggedIn = false;
const command = pool.sendCommand('BotA', 'u claimmode');
assert.strictEqual(command.ok, false);
assert.ok(command.message.includes('未完成登录验证'));
@@ -157,6 +173,36 @@ test('未完成登录验证时拒绝发送指令与聊天', () => {
assert.ok(chat.message.includes('未完成登录验证'));
});
test('未登录时验证类指令仍可下发(否则无法完成登录)', () => {
const { pool } = setup();
const entry = pool.get('BotA');
const sent = [];
entry.manager.bot = { chat: (text) => sent.push(text) };
entry.manager.state = 'online';
entry.manager.loggedIn = false;
const result = pool.sendCommand('BotA', '/auth 12345 6789');
assert.strictEqual(result.ok, true);
// 指令有节流,这里直接驱动写入验证内容
entry.manager._commandQueue.length = 0;
entry.manager._writeCommand('auth 12345 6789');
assert.deepStrictEqual(sent, ['/auth 12345 6789']);
});
test('空指令被拒绝', () => {
const { pool } = setup();
const result = pool.sendCommand('BotA', ' ');
assert.strictEqual(result.ok, false);
assert.ok(result.message.includes('指令为空'));
});
test('空聊天消息被拒绝', () => {
const { pool } = setup();
const result = pool.sendChat('BotA', ' ');
assert.strictEqual(result.ok, false);
assert.ok(result.message.includes('消息为空'));
});
test('完成登录验证后允许发送指令与聊天', () => {
const { pool } = setup();
const entry = pool.get('BotA');