Files
IC-Coder-Plugin/docs/token-expiration-check.md
Roe-xin 9296b10150 feat:实现Token过期检查和自动清除机制
主要改动:
   - 在插件激活时检查Token是否过期,过期则自动清除session
   - 修复Token检查逻辑,从session.accessToken获取Token而非globalState
   - 在消息发送前检查Token有效性,过期则提示重新登录
   - 优化ICHelperPanel和ICViewProvider的Token过期处理
   - 修复退出登录命令名错误(iccoder.logout -> ic-coder.logout)
   - 添加Token过期检查文档文档
2026-01-26 18:41:52 +08:00

7.3 KiB
Raw Blame History

Token 过期检查实现方案

1. 概述

实现三个关键时机的 Token 过期检查:

  • 插件激活时
  • 发起 API 请求前
  • 用户交互时(打开面板/侧边栏)

2. 数据存储

2.1 存储位置

使用 VS Code 的 globalState 存储:

context.globalState.update('tokenExp', exp);

2.2 存储内容

  • token: 用户 token
  • tokenExp: 过期时间戳(秒)
  • userInfo: 用户信息

3. 核心函数设计

3.1 过期检查函数

/**
 * 检查 token 是否过期
 * @param exp - 过期时间戳(秒)
 * @param bufferSeconds - 提前判断过期的缓冲时间(默认 60 秒)
 * @returns true 表示已过期或即将过期
 */
function isTokenExpired(exp: number | undefined, bufferSeconds: number = 60): boolean {
  if (!exp) {
    return true; // 没有过期时间,视为已过期
  }

  const now = Math.floor(Date.now() / 1000); // 当前时间戳(秒)
  return now >= (exp - bufferSeconds); // 提前 60 秒判断过期
}

3.2 清除登录状态函数

/**
 * 清除所有登录相关状态
 */
async function clearAuthState(context: vscode.ExtensionContext): Promise<void> {
  await context.globalState.update('token', undefined);
  await context.globalState.update('tokenExp', undefined);
  await context.globalState.update('userInfo', undefined);
}

3.3 统一过期处理函数

/**
 * 处理 token 过期情况
 * @param context - 扩展上下文
 * @param showMessage - 是否显示提示消息
 */
async function handleTokenExpired(
  context: vscode.ExtensionContext,
  showMessage: boolean = true
): Promise<void> {
  await clearAuthState(context);

  if (showMessage) {
    const action = await vscode.window.showWarningMessage(
      '登录已过期,请重新登录',
      '立即登录'
    );

    if (action === '立即登录') {
      // 触发登录流程(打开登录面板)
      vscode.commands.executeCommand('ic-coder.openPanel');
    }
  }
}

4. 三个检查时机实现

4.1 插件激活时检查

位置: src/extension.tsactivate 函数

实现:

export async function activate(context: vscode.ExtensionContext) {
  console.log('IC Coder 插件正在激活...');

  // 1. 检查 token 是否过期
  const tokenExp = context.globalState.get<number>('tokenExp');
  if (isTokenExpired(tokenExp)) {
    // 静默清除,不显示提示(避免启动时打扰用户)
    await handleTokenExpired(context, false);
  }

  // ... 其他激活逻辑
}

说明: 启动时静默检查,如果过期则清除状态,但不弹窗提示


4.2 发起 API 请求前检查

位置: src/utils/messageHandler.ts 的 API 请求函数

实现:

// 在发送消息到后端前检查
async function sendMessageToBackend(message: string, context: vscode.ExtensionContext) {
  // 1. 检查 token 是否过期
  const tokenExp = context.globalState.get<number>('tokenExp');
  if (isTokenExpired(tokenExp)) {
    await handleTokenExpired(context, true); // 显示提示
    return; // 中断请求
  }

  const token = context.globalState.get<string>('token');
  if (!token) {
    vscode.window.showWarningMessage('请先登录');
    return;
  }

  // 2. 继续发送请求
  // ... 原有请求逻辑
}

说明: 每次 API 请求前检查,如果过期则提示用户并中断请求


4.3 用户交互时检查

位置:

  • src/panels/ICHelperPanel.ts - 打开聊天面板时
  • src/views/ICViewProvider.ts - 侧边栏视图加载时

实现 - 聊天面板:

// ICHelperPanel.ts
public static render(extensionUri: vscode.Uri, context: vscode.ExtensionContext) {
  // 1. 检查 token 是否过期
  const tokenExp = context.globalState.get<number>('tokenExp');
  if (isTokenExpired(tokenExp)) {
    handleTokenExpired(context, true); // 显示提示
    // 继续渲染面板,但会显示未登录状态
  }

  // 2. 创建或显示面板
  // ... 原有逻辑
}

实现 - 侧边栏视图:

// ICViewProvider.ts
public resolveWebviewView(webviewView: vscode.WebviewView) {
  // 1. 检查 token 是否过期
  const tokenExp = this._context.globalState.get<number>('tokenExp');
  if (isTokenExpired(tokenExp)) {
    handleTokenExpired(this._context, false); // 静默清除
    // 继续渲染,显示未登录状态
  }

  // 2. 渲染视图
  // ... 原有逻辑
}

说明: 打开面板时检查,聊天面板显示提示,侧边栏静默处理

5. 后端响应处理

5.1 保存 exp 字段

位置: src/utils/messageHandler.ts 处理登录响应的地方

实现:

// 处理登录成功响应
if (response.data.token) {
  await context.globalState.update('token', response.data.token);

  // 保存过期时间
  if (response.data.exp) {
    await context.globalState.update('tokenExp', response.data.exp);
  }

  // 保存用户信息
  if (response.data.userInfo) {
    await context.globalState.update('userInfo', response.data.userInfo);
  }
}

5.2 处理 401 响应

实现:

// API 请求错误处理
if (error.response?.status === 401) {
  // 后端返回 401说明 token 无效或过期
  await handleTokenExpired(context, true);
  return;
}

6. 工具函数位置

建议创建新文件 src/utils/authHelper.ts:

import * as vscode from 'vscode';

export function isTokenExpired(exp: number | undefined, bufferSeconds: number = 60): boolean {
  if (!exp) {
    return true;
  }
  const now = Math.floor(Date.now() / 1000);
  return now >= (exp - bufferSeconds);
}

export async function clearAuthState(context: vscode.ExtensionContext): Promise<void> {
  await context.globalState.update('token', undefined);
  await context.globalState.update('tokenExp', undefined);
  await context.globalState.update('userInfo', undefined);
}

export async function handleTokenExpired(
  context: vscode.ExtensionContext,
  showMessage: boolean = true
): Promise<void> {
  await clearAuthState(context);

  if (showMessage) {
    const action = await vscode.window.showWarningMessage(
      '登录已过期,请重新登录',
      '立即登录'
    );

    if (action === '立即登录') {
      vscode.commands.executeCommand('ic-coder.openPanel');
    }
  }
}

7. 测试场景

  1. 启动测试: 设置过期的 exp重启插件验证状态被清除
  2. 请求测试: 设置即将过期的 exp发送消息验证被拦截
  3. 交互测试: 设置过期的 exp打开面板验证提示显示
  4. 401 测试: 模拟后端返回 401验证状态清除

8. 注意事项

  • 使用 60 秒缓冲时间,避免请求中途过期
  • 启动和侧边栏加载时静默处理,避免打扰用户
  • 主动操作(发消息、打开聊天面板)时显示提示
  • 所有时间戳使用秒为单位(与后端保持一致)
  • 过期检查应该在所有需要 token 的操作前执行

9. 修改文件清单

需要修改的文件:

  1. 新建: src/utils/authHelper.ts - 认证辅助工具函数
  2. 修改: src/extension.ts - 插件激活时检查
  3. 修改: src/utils/messageHandler.ts - API 请求前检查 + 保存 exp + 处理 401
  4. 修改: src/panels/ICHelperPanel.ts - 打开聊天面板时检查
  5. 修改: src/views/ICViewProvider.ts - 侧边栏加载时检查