# Token

`Token` 持有机器人的 `app_id` 与 `secret`，负责签名 API 请求，并把访问令牌缓存到所有 `Token` 克隆共享的状态里。缓存按需刷新，受内部 mutex 保护，因此 `Token` 在多任务之间共享非常安全且开销很小。

```rust
pub struct Token { /* 私有字段 */ }
```

`Token` 实现了 `Clone`、`Debug`、`Serialize`、`Deserialize`。序列化时缓存会被丢弃，便于在配置文件里往返携带凭证。

## 构造

- `Token::new(app_id, secret)` —— 显式凭证。
- `Token::from_env()` —— 读取 `QQ_BOT_APP_ID` / `QQ_BOT_SECRET`，缺失时返回 `BotError::Config`。

```rust
let token = Token::new("123456789", "abcdef...");
let token = Token::from_env()?;       // 走环境变量
```

## 访问器

- `app_id()` / `secret()` —— 借用凭证字符串（`&str`）。
- `safe_display()` —— 返回适合日志的脱敏字符串（如 `Token { app_id: "1234***", secret: "***" }`），任何想打印 token 的地方都应改用这个。

## 鉴权

- `authorization_header().await` —— 构造 `Authorization: QQBot <access-token>`，过期时会自动刷新。
- `bot_token().await` —— 只返回 bot token 本体（不含 `QQBot ` 前缀），由网关 `Identify` 载荷使用。

两者共用同一份访问令牌缓存。并发调用方在需要刷新时会短暂地等待 refresh mutex，然后无竞争地读到最新值。

## 校验

`validate()` 做轻量的语法检查（app id 非空、长度合理等），失败时返回 `BotError::Config`。建议在 `Token::new(...)` 之后立即调用，启动阶段就把凭证错误暴露出来。

## 刷新机制

框架会监控访问令牌的有效期，在到期前自动续期。如果续期失败，下一个调用方会收到 `BotError::Auth` 并附带上游错误信息。`Token` 没有公开的 `refresh()` 方法——刷新由 `authorization_header()` / `bot_token()` 自己驱动。

由于克隆共享同一份 `Arc<Mutex<TokenState>>`，任何一个克隆触发的刷新都会立刻对其他克隆可见，即便在高并发下，一个有效期窗口内也只刷新一次。

## 示例

**从环境变量构造并在启动时校验一次：**

```rust
let token = Token::from_env()?;
token.validate()?;
```

**手工构造 HTTP 请求：**

```rust
let auth = token.authorization_header().await?;
let request = client
    .get("https://api.example.com/whatever")
    .header("Authorization", auth)
    .send()
    .await?;
```

**安全打印 token：**

```rust
info!("starting bot {}", token.safe_display());
```

## 安全建议

- 永远不要 `Debug`-print `Token`，请走 `safe_display()`。
- 凭证优先来自环境变量或秘钥管理服务，不要提交到代码仓库的配置文件里。
- 若怀疑密钥泄漏，先在 QQ 开放平台轮换密钥；轮换后下一次 `authorization_header()` 会返回 `BotError::Auth`，再用新密钥构造新的 `Token` 即可。

## 参见

- [Bot API](./bot-api.md) —— 所有接口最终都会消费 `Token`。
- [Context](./context.md) —— 内嵌 `Token`，省去回调里手动传参。
- [错误类型](./error-types.md) —— 上文涉及的 `BotError::Auth` 与 `BotError::Config` 行为。
