feat: 支持多用户与用户级MCP隔离,优化汇总诊断与UI展示

- 多用户支持:新增 user_mcp_tokens 表,实现按用户 Token 哈希隔离 MCP 查询
- 注册控制:支持 ALLOW_REGISTER 环境变量自适应开关多用户注册
- 前端同步:增加跨账号防脏提交安全拦截,优化登录/注册状态提示
- 汇总页优化:移除合计应得卡片冗余说明,诊断卡片新增「综合时薪诊断」
- 文档更新:同步更新 README.md 关于多用户、MCP 与部署说明
This commit is contained in:
OpenClaw Agent
2026-09-14 14:00:07 +08:00
parent ad36640331
commit 53733b39fc
10 changed files with 320 additions and 63 deletions
+47 -2
View File
@@ -39,12 +39,12 @@ async function rpc(msg, token) {
// 管理 API:与 api.test.js 同一套调用模式
let TOKEN = null;
async function call(method, path, body, token = TOKEN) {
async function call(method, path, body, token = TOKEN, opts = { allowRegister: true }) {
const headers = { 'content-type': 'application/json' };
if (token) headers.authorization = `Bearer ${token}`;
const res = await handleApi(new Request('http://t' + path, {
method, headers, body: body === undefined ? undefined : JSON.stringify(body)
}), store, {});
}), store, opts);
let json = null;
try { json = await res.json(); } catch {}
return { status: res.status, body: json };
@@ -168,6 +168,51 @@ section('Token 轮换:旧的立即失效');
eq('取回明文是新 Token', r.body.token, globalThis.__T2);
}
// ==========================================================
section('多用户 MCP 隔离:不同 Token 独立绑定不同用户');
{
// 注册第二个用户 user2
let r = await call('POST', '/api/register', { username: 'alice', password: 'alice-pass-123' }, null, { allowRegister: true });
eq('第二个用户注册 201', r.status, 201);
r = await call('POST', '/api/login', { username: 'alice', password: 'alice-pass-123' }, null);
eq('第二个用户登录 200', r.status, 200);
const user2Token = r.body.token;
// 用户2 推送自己的打卡数据(9/10 10小时)
await call('POST', '/api/push', {
records: {
'2026-09-10': { status: 'work', shift_type: 'day', hours: 10 }
}
}, user2Token);
// 用户1 推送自己的打卡数据(9/10 6小时)
await call('POST', '/api/push', {
records: {
'2026-09-10': { status: 'work', shift_type: 'day', hours: 6 }
}
}, TOKEN);
// 用户2 生成自己的 MCP Token
r = await call('POST', '/api/mcp/token', undefined, user2Token);
eq('用户2 生成 MCP Token 200', r.status, 200);
const user2McpToken = r.body.token;
ok('用户2 的 MCP Token 与用户1 不同', user2McpToken !== globalThis.__T2);
// 用 用户1 的 MCP Token 查 9月总结
const CALL_SUMMARY = {
jsonrpc: '2.0', id: 2, method: 'tools/call',
params: { name: 'get_month_summary', arguments: { year: 2026, month: 9 } }
};
const r1 = await rpc(CALL_SUMMARY, globalThis.__T2);
eq('用户1 MCP 调用成功', r1.status, 200);
eq('用户1 工时是 6h', r1.body.result.structuredContent.hours.total, 6);
// 用 用户2 的 MCP Token 查 9月总结
const r2 = await rpc(CALL_SUMMARY, user2McpToken);
eq('用户2 MCP 调用支持成功', r2.status, 200);
eq('用户2 工时是 10h', r2.body.result.structuredContent.hours.total, 10);
}
// ==========================================================
store.close();
rmSync(tmp, { recursive: true, force: true });