/** * 用户服务 * 管理用户信息和认证相关的 API 调用 */ import * as https from 'https'; import * as http from 'http'; import { URL } from 'url'; import * as vscode from 'vscode'; import { getStrangeLoopApiUrl, getConfig } from '../config/settings'; import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api'; import { fetchBalanceWithToken, getCachedBalance } from './creditsService'; /** * HTTP 请求选项 */ interface RequestOptions { method: 'GET' | 'POST' | 'PUT' | 'DELETE'; headers?: Record; body?: unknown; timeout?: number; token?: string; } /** * 发送 HTTP 请求(带 token) */ async function request(path: string, options: RequestOptions): Promise { const url = new URL(getStrangeLoopApiUrl(path)); const { timeout } = getConfig(); const isHttps = url.protocol === 'https:'; const httpModule = isHttps ? https : http; const headers: Record = { 'Content-Type': 'application/json', ...options.headers }; // 如果有 token,添加到请求头 if (options.token) { headers['Authorization'] = `Bearer ${options.token}`; } const requestOptions: http.RequestOptions = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 80), path: url.pathname + url.search, method: options.method, headers, timeout: options.timeout || timeout }; return new Promise((resolve, reject) => { const req = httpModule.request(requestOptions, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { console.log(`[HTTP] 响应状态码: ${res.statusCode}`); console.log(`[HTTP] 响应内容: ${data}`); try { const json = JSON.parse(data); if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { resolve(json as T); } else { reject(new Error(json.error || json.message || json.msg || `HTTP ${res.statusCode}`)); } } catch (e) { // 如果不是 JSON,直接返回原始内容 reject(new Error(`解析响应失败 (${res.statusCode}): ${data}`)); } }); }); req.on('error', (error) => { reject(error); }); req.on('timeout', () => { req.destroy(); reject(new Error('请求超时')); }); if (options.body) { req.write(JSON.stringify(options.body)); } req.end(); }); } /** * 用户信息数据结构(实际返回的数据) */ export interface UserInfo { userId: string; username: string; nickname: string; email?: string; phonenumber?: string; avatar?: string; roles?: string[]; permissions?: string[]; createTime?: string; loginDate?: string; // 会员信息 membership?: { tierCode: string; tierName: string; tierLevel: number; remainingDays?: number; monthlyCredits?: number; }; // Credits 余额 credits?: number; // 插件试用用户标识(从 JWT token 中提取) isPluginTrial?: boolean; // 试用到期时间(毫秒时间戳) pluginTrialExpiresAt?: number; } /** * 获取用户信息 * GET /system/user/getInfo */ export async function getUserInfo(token: string): Promise { const apiPath = '/system/user/getInfo'; const fullUrl = getStrangeLoopApiUrl(apiPath); console.log('[UserService] 获取用户信息'); console.log('[UserService] 请求地址:', fullUrl); console.log('[UserService] Token:', token ? '已提供' : '未提供'); try { const response = await request(apiPath, { method: 'GET', token }); // 处理响应数据 - 检查 code 是否为 200 if (response.code === 200 && response.user) { const user = response.user; const userInfo: UserInfo = { userId: String(user.userId), username: user.userName, nickname: user.nickName, email: user.email, phonenumber: user.phonenumber, avatar: user.avatar, roles: response.roles, permissions: response.permissions, createTime: user.createTime, loginDate: user.loginDate }; // 从接口响应中获取企业试用标识 if (response.isPluginTrial === true) { userInfo.isPluginTrial = true; console.log('[UserService] 从 getInfo 接口获取到 isPluginTrial: true'); } // 获取试用到期时间(null 表示长期有效) if (response.enterpriseTrialExpires !== undefined) { userInfo.pluginTrialExpiresAt = response.enterpriseTrialExpires; if (response.enterpriseTrialExpires === null) { console.log('[UserService] 试用长期有效'); } else { console.log('[UserService] 试用到期时间:', new Date(response.enterpriseTrialExpires).toLocaleString()); } } return userInfo; } console.error('[UserService] 获取用户信息失败:', response); return null; } catch (error) { console.error('[UserService] 请求失败:', error); return null; } } /** * 获取用户会员信息 * GET /strangeloop/api/membership/current */ export async function getMembershipInfo(token: string): Promise { const apiPath = '/strangeloop/api/membership/current'; const fullUrl = getStrangeLoopApiUrl(apiPath); console.log('[UserService] 获取会员信息'); console.log('[UserService] 请求地址:', fullUrl); console.log('[UserService] Token:', token ? '已提供' : '未提供'); try { const response = await request(apiPath, { method: 'GET', token }); // 处理响应数据 - 检查 code 是否为 200 if (response.code === 200 && response.data) { console.log('[UserService] 会员信息获取成功:', response.data); return response.data; } console.error('[UserService] 获取会员信息失败:', response); return null; } catch (error) { console.error('[UserService] 请求会员信息失败:', error); return null; } } /** * 会员等级映射 */ const TIER_LEVEL_MAP: Record = { 'BASIC': 1, 'TRIAL': 2, 'ADVANCED': 3, 'PROFESSIONAL': 4 }; /** * 获取最高等级的会员信息 */ function getHighestTierMembership(allMemberships?: MembershipItemVO[]): MembershipItemVO | null { if (!allMemberships || allMemberships.length === 0) { return null; } // 按等级排序,获取最高等级 return allMemberships.reduce((highest, current) => { const currentLevel = TIER_LEVEL_MAP[current.tierCode] || 0; const highestLevel = TIER_LEVEL_MAP[highest.tierCode] || 0; return currentLevel > highestLevel ? current : highest; }); } /** * 当获取到 token 时自动调用此函数 * 用于在登录成功后立即获取用户信息 */ export async function onTokenReceived(token: string): Promise { try { console.log('[UserService] Token 已获取,正在获取用户信息、会员信息和余额...'); // 并行获取用户信息、会员信息和余额 const [userInfo, membershipInfo, credits] = await Promise.all([ getUserInfo(token), getMembershipInfo(token), fetchBalanceWithToken(token) ]); if (!userInfo) { console.warn('[UserService] 未能获取到用户信息'); return null; } // 添加 Credits 余额到用户信息 console.log('[UserService] 获取到的 Credits 余额:', credits); if (credits !== null) { userInfo.credits = credits; console.log('[UserService] Credits 已添加到用户信息'); } else { console.warn('[UserService] Credits 余额为 null,未添加到用户信息'); } // 打印用户信息到控制台 console.log('='.repeat(60)); console.log('用户信息详情:'); console.log('='.repeat(60)); console.log(`用户ID: ${userInfo.userId}`); console.log(`用户名: ${userInfo.username}`); console.log(`昵称: ${userInfo.nickname}`); if (userInfo.email) { console.log(`邮箱: ${userInfo.email}`); } if (userInfo.phonenumber) { console.log(`手机号: ${userInfo.phonenumber}`); } if (userInfo.avatar) { console.log(`头像: ${userInfo.avatar}`); } if (userInfo.roles && userInfo.roles.length > 0) { console.log(`角色: ${userInfo.roles.join(', ')}`); } if (userInfo.permissions && userInfo.permissions.length > 0) { console.log(`权限: ${userInfo.permissions.join(', ')}`); } if (userInfo.createTime) { console.log(`创建时间: ${userInfo.createTime}`); } if (userInfo.loginDate) { console.log(`最后登录: ${userInfo.loginDate}`); } // 打印会员信息 - 从 allMemberships 中获取最高等级 if (membershipInfo && membershipInfo.allMemberships) { const highestTier = getHighestTierMembership(membershipInfo.allMemberships); if (highestTier) { console.log(''); console.log('会员信息:'); console.log(`会员等级: ${highestTier.tierName} (${highestTier.tierCode})`); console.log(`等级层级: ${highestTier.tierLevel}`); console.log(`剩余天数: ${highestTier.remainingDays === -1 ? '永久' : highestTier.remainingDays + '天'}`); console.log(`月度积分: ${highestTier.monthlyCredits}`); // 将最高等级会员信息合并到用户信息中 userInfo.membership = { tierCode: highestTier.tierCode, tierName: highestTier.tierName, tierLevel: highestTier.tierLevel, remainingDays: highestTier.remainingDays, monthlyCredits: highestTier.monthlyCredits }; } } // 打印 Credits 余额 console.log(''); console.log('资源点余额:'); if (userInfo.credits !== undefined) { console.log(`当前余额: ${userInfo.credits} Credits`); } else { console.log('当前余额: 未获取到余额信息'); } console.log('='.repeat(60)); // 保存到持久化存储 await saveUserInfo(userInfo); // 判断是否是插件试用用户 console.log('[UserService] 检查用户类型,isPluginTrial:', userInfo.isPluginTrial); console.log('[UserService] extensionContext 是否存在:', !!extensionContext); if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) { // 检查是否过期 const now = Date.now(); const isExpired = now >= userInfo.pluginTrialExpiresAt; console.log('[UserService] 试用到期时间:', new Date(userInfo.pluginTrialExpiresAt).toLocaleString()); console.log('[UserService] 当前时间:', new Date(now).toLocaleString()); console.log('[UserService] 是否过期:', isExpired); if (isExpired) { // 已过期:显示邀请码弹窗 console.log('[UserService] 试用已过期,将显示邀请码弹窗'); } else { // 未过期:显示欢迎弹窗 const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed'); console.log('[UserService] 是否已显示过欢迎弹窗:', hasWelcomed); if (!hasWelcomed && extensionContext) { await extensionContext.globalState.update('showWelcomeModal', true); await extensionContext.globalState.update('pluginTrialWelcomed', true); console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true'); const checkMark = extensionContext.globalState.get('showWelcomeModal'); console.log('[UserService] 验证标记:', checkMark); } else if (!extensionContext) { console.error('[UserService] ❌ extensionContext 为 null,无法设置标记'); } else { console.log('[UserService] 已经显示过欢迎弹窗,跳过'); } } } else { // isPluginTrial=false 或 enterpriseTrialExpires 为 null:显示邀请码弹窗 console.log('[UserService] 非试用用户或无过期时间,将显示邀请码弹窗'); } return userInfo; } catch (error) { console.error('[UserService] 获取用户信息失败:', error); return null; } } // ============== 持久化存储 ============== let extensionContext: vscode.ExtensionContext | null = null; /** * 初始化用户服务(设置 context) */ export function initUserService(context: vscode.ExtensionContext): void { extensionContext = context; } /** * 保存用户信息到持久化存储 */ export async function saveUserInfo(userInfo: UserInfo): Promise { if (!extensionContext) { console.warn('[UserService] ExtensionContext 未初始化'); return; } await extensionContext.globalState.update('icCoderUserInfo', userInfo); console.log('[UserService] 用户信息已保存到持久化存储'); } /** * 从持久化存储获取用户信息 */ export function getCachedUserInfo(): UserInfo | null { if (!extensionContext) { console.warn('[UserService] ExtensionContext 未初始化'); return null; } const userInfo = extensionContext.globalState.get('icCoderUserInfo') || null; // 从 creditsService 加载余额并合并到用户信息中 if (userInfo) { const cachedCredits = getCachedBalance(); if (cachedCredits !== null) { userInfo.credits = cachedCredits; console.log('[UserService] 从 creditsService 加载余额:', cachedCredits); } } return userInfo; } /** * 清除持久化存储的用户信息 */ export async function clearUserInfo(): Promise { if (!extensionContext) { console.warn('[UserService] ExtensionContext 未初始化'); return; } await extensionContext.globalState.update('icCoderUserInfo', undefined); console.log('[UserService] 用户信息已清除'); }