You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
61 lines
2.4 KiB
61 lines
2.4 KiB
import '../../core/utils/logger.dart';
|
|
import 'network/api.dart';
|
|
|
|
/// EMAI 会话用的 MCP 令牌。
|
|
///
|
|
/// 百炼调我们的 MCP 工具(查会议纪要、查待办…)时,需要知道「这是哪个用户」。
|
|
/// 整条链路上唯一能按用户变的通道是 Start 指令的
|
|
/// `biz_params.user_defined_params` —— 客户端连百炼只带百炼的 API Key,
|
|
/// 百炼调 MCP 的 Authorization 头又是控制台里配死的、所有用户共用。
|
|
///
|
|
/// ⚠️ 这里拿的是服务端 `user_getmcptoken` 签发的**短时效、仅限 MCP 用途**的令牌,
|
|
/// 不是登录 JWT —— 这条路要经过百炼(第三方),登录 JWT 等价于账号全部权限。
|
|
class McpTokenService {
|
|
const McpTokenService._();
|
|
|
|
static const String _tag = 'McpToken';
|
|
|
|
static String? _token;
|
|
static int _expiresAt = 0;
|
|
|
|
/// 提前多久就当它过期。会话可能持续很久,卡着过期时刻去用必然中途失效。
|
|
static const int _skewSeconds = 300;
|
|
|
|
static bool get _fresh {
|
|
if (_token == null || _token!.isEmpty) return false;
|
|
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
|
return _expiresAt - _skewSeconds > now;
|
|
}
|
|
|
|
/// 取令牌。缓存未过期就直接用,否则重新申请。
|
|
///
|
|
/// 拿不到时返回 null 而不是抛异常 —— MCP 工具用不了只是少几项能力,
|
|
/// 不该因此让整个 EMAI 会话建立不起来。
|
|
static Future<String?> get() async {
|
|
if (_fresh) return _token;
|
|
try {
|
|
final resp = await Api.getMcpToken();
|
|
// ⚠️ AuthInterceptor 在业务码 != 0 时是把 data 置 null 后 resolve(不抛),
|
|
// 所以这里必须判 null;只 catch 是接不住业务错误的。
|
|
if (resp == null) {
|
|
Logger.w(_tag, '服务端未签发 MCP 令牌(多半是没配 McpTokenKey)');
|
|
return null;
|
|
}
|
|
final token = (resp['token'] ?? '').toString();
|
|
if (token.isEmpty) return null;
|
|
_token = token;
|
|
_expiresAt = (resp['expiresat'] as num?)?.toInt() ?? 0;
|
|
Logger.i(_tag, 'MCP 令牌已更新,过期时刻=$_expiresAt');
|
|
return _token;
|
|
} catch (e) {
|
|
Logger.w(_tag, '申请 MCP 令牌失败(MCP 工具本次不可用): $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// 换账号必须清掉,否则新账号会拿着上一个人的令牌去调 MCP。
|
|
static void reset() {
|
|
_token = null;
|
|
_expiresAt = 0;
|
|
}
|
|
}
|
|
|