Files
IC-Coder-Plugin/src/services/userService.ts
Roe-xin 76817675f1 fix: 修复试用用户欢迎弹窗不显示的问题
- 修复 userService 中 null 值未正确赋值的问题
- 优化欢迎弹窗判断逻辑:null=长期有效,undefined=无效
- 添加测试命令 resetWelcomeModal 用于清除弹窗标记
2026-03-03 19:06:02 +08:00

439 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 用户服务
* 管理用户信息和认证相关的 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<string, string>;
body?: unknown;
timeout?: number;
token?: string;
}
/**
* 发送 HTTP 请求(带 token
*/
async function request<T>(path: string, options: RequestOptions): Promise<T> {
const url = new URL(getStrangeLoopApiUrl(path));
const { timeout } = getConfig();
const isHttps = url.protocol === 'https:';
const httpModule = isHttps ? https : http;
const headers: Record<string, string> = {
'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<UserInfo | null> {
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<UserInfoResponse>(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<MultiMembershipVO | null> {
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<MembershipResponse>(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<string, number> = {
'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<UserInfo | null> {
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<void> {
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<UserInfo>('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<void> {
if (!extensionContext) {
console.warn('[UserService] ExtensionContext 未初始化');
return;
}
await extensionContext.globalState.update('icCoderUserInfo', undefined);
console.log('[UserService] 用户信息已清除');
}