692 lines
23 KiB
TypeScript
692 lines
23 KiB
TypeScript
import {Context} from "hono";
|
||
import * as local from "hono/cookie";
|
||
|
||
// 阿里云盘扫码登录相关接口定义
|
||
interface QRCodeData {
|
||
qrCodeUrl: string;
|
||
ck?: string;
|
||
t?: string;
|
||
resultCode?: number;
|
||
processFinished?: boolean;
|
||
}
|
||
|
||
interface QRStatusResponse {
|
||
success: boolean;
|
||
content: {
|
||
qrCodeStatus: string;
|
||
resultCode: number;
|
||
bizExt?: any;
|
||
data?: any;
|
||
};
|
||
}
|
||
|
||
interface UserInfo {
|
||
user_id: string;
|
||
nick_name: string;
|
||
avatar: string;
|
||
phone: string;
|
||
email: string;
|
||
}
|
||
|
||
interface DriveInfo {
|
||
total_size: number;
|
||
used_size: number;
|
||
album_drive_used_size: number;
|
||
note_drive_used_size: number;
|
||
}
|
||
|
||
// 阿里云盘API响应接口
|
||
interface AliCloudApiResponse {
|
||
hasError?: boolean;
|
||
content?: {
|
||
success?: boolean;
|
||
data?: {
|
||
codeContent?: string;
|
||
ck?: string;
|
||
t?: string;
|
||
resultCode?: number;
|
||
processFinished?: boolean;
|
||
qrCodeStatus?: string;
|
||
bizExt?: any;
|
||
};
|
||
};
|
||
}
|
||
|
||
// 阿里云盘扫码登录类
|
||
class AlipanQRLogin {
|
||
private session_id: string;
|
||
private csrf_token: string;
|
||
private umid_token: string;
|
||
private qr_code_data: QRCodeData | null = null;
|
||
private access_token: string | null = null;
|
||
private refresh_token: string | null = null;
|
||
|
||
constructor() {
|
||
this.session_id = this.generateUUID();
|
||
this.csrf_token = "MuSysYVxW5AMGblcOTSKb3";
|
||
this.umid_token = this.generateUUID().replace(/-/g, '');
|
||
}
|
||
|
||
private generateUUID(): string {
|
||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
const r = Math.random() * 16 | 0;
|
||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||
return v.toString(16);
|
||
});
|
||
}
|
||
|
||
private getHeaders(): Record<string, string> {
|
||
return {
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||
'Accept': 'application/json, text/plain, */*',
|
||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||
'Accept-Encoding': 'gzip, deflate, br',
|
||
'Connection': 'keep-alive',
|
||
'Sec-Fetch-Dest': 'empty',
|
||
'Sec-Fetch-Mode': 'cors',
|
||
'Sec-Fetch-Site': 'same-site',
|
||
'Referer': 'https://passport.alipan.com/',
|
||
'Origin': 'https://passport.alipan.com'
|
||
};
|
||
}
|
||
|
||
// 获取OAuth认证URL
|
||
async getOAuthUrl(): Promise<string | null> {
|
||
try {
|
||
const authUrl = "https://auth.alipan.com/v2/oauth/authorize";
|
||
const params = new URLSearchParams({
|
||
'client_id': '25dzX3vbYqktVxyX',
|
||
'redirect_uri': 'https://www.alipan.com/sign/callback',
|
||
'response_type': 'code',
|
||
'login_type': 'custom',
|
||
'state': '{"origin":"https://www.alipan.com"}'
|
||
});
|
||
|
||
const response = await fetch(`${authUrl}?${params}`, {
|
||
method: 'GET',
|
||
headers: this.getHeaders()
|
||
});
|
||
|
||
if (response.ok) {
|
||
return response.url;
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('获取OAuth URL失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取登录页面信息
|
||
async getLoginPage(): Promise<boolean> {
|
||
try {
|
||
const loginUrl = "https://passport.alipan.com/mini_login.htm";
|
||
const params = new URLSearchParams({
|
||
'lang': 'zh_cn',
|
||
'appName': 'aliyun_drive',
|
||
'appEntrance': 'web_default',
|
||
'styleType': 'auto',
|
||
'bizParams': '',
|
||
'notLoadSsoView': 'false',
|
||
'notKeepLogin': 'false',
|
||
'isMobile': 'false',
|
||
'ad__pass__q__rememberLogin': 'true',
|
||
'ad__pass__q__rememberLoginDefaultValue': 'true',
|
||
'ad__pass__q__forgotPassword': 'true',
|
||
'ad__pass__q__licenseMargin': 'true',
|
||
'ad__pass__q__loginType': 'normal',
|
||
'hidePhoneCode': 'true',
|
||
'rnd': Date.now().toString()
|
||
});
|
||
|
||
const response = await fetch(`${loginUrl}?${params}`, {
|
||
method: 'GET',
|
||
headers: this.getHeaders()
|
||
});
|
||
|
||
if (response.ok) {
|
||
const content = await response.text();
|
||
|
||
// 尝试提取CSRF token
|
||
const csrfMatch = content.match(/_csrf_token["']?\s*[:=]\s*["']([^"']+)["']/);
|
||
if (csrfMatch) {
|
||
this.csrf_token = csrfMatch[1];
|
||
}
|
||
|
||
// 尝试提取umidToken
|
||
const umidMatch = content.match(/umidToken["']?\s*[:=]\s*["']([^"']+)["']/);
|
||
if (umidMatch) {
|
||
this.umid_token = umidMatch[1];
|
||
}
|
||
|
||
return true;
|
||
}
|
||
return false;
|
||
} catch (error) {
|
||
console.error('获取登录页面失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 生成二维码
|
||
async generateQRCode(): Promise<QRCodeData | null> {
|
||
try {
|
||
const qrUrl = "https://passport.alipan.com/newlogin/qrcode/generate.do";
|
||
const params = new URLSearchParams({
|
||
'appName': 'aliyun_drive',
|
||
'fromSite': '52',
|
||
'appEntrance': 'web_default',
|
||
'_csrf_token': this.csrf_token,
|
||
'umidToken': this.umid_token,
|
||
'hsiz': '115d9f5f2cf2f87850a93a793aaaecb4',
|
||
'bizParams': 'taobaoBizLoginFrom=web_default&renderRefer=https%3A%2F%2Fauth.alipan.com%2F',
|
||
'mainPage': 'false',
|
||
'isMobile': 'false',
|
||
'lang': 'zh_CN',
|
||
'returnUrl': '',
|
||
'umidTag': 'SERVER'
|
||
});
|
||
|
||
const headers = {
|
||
...this.getHeaders(),
|
||
'X-Requested-With': 'XMLHttpRequest',
|
||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||
};
|
||
|
||
const response = await fetch(`${qrUrl}?${params}`, {
|
||
method: 'GET',
|
||
headers: headers
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json() as AliCloudApiResponse;
|
||
|
||
if (!result.hasError) {
|
||
const content = result.content || {};
|
||
if (content.success) {
|
||
const data = content.data || {};
|
||
const codeContent = data.codeContent;
|
||
|
||
if (codeContent) {
|
||
this.qr_code_data = {
|
||
qrCodeUrl: codeContent,
|
||
ck: data.ck,
|
||
t: data.t,
|
||
resultCode: data.resultCode,
|
||
processFinished: data.processFinished
|
||
};
|
||
return this.qr_code_data;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('生成二维码失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 查询二维码状态
|
||
async queryQRStatus(): Promise<QRStatusResponse | null> {
|
||
try {
|
||
if (!this.qr_code_data) {
|
||
return null;
|
||
}
|
||
|
||
const queryUrl = "https://passport.alipan.com/newlogin/qrcode/query.do";
|
||
const formData = new URLSearchParams({
|
||
'appName': 'aliyun_drive',
|
||
'fromSite': '52'
|
||
});
|
||
|
||
if (this.qr_code_data.ck) {
|
||
formData.append('ck', this.qr_code_data.ck);
|
||
}
|
||
if (this.qr_code_data.t) {
|
||
formData.append('t', this.qr_code_data.t);
|
||
}
|
||
|
||
const headers = {
|
||
...this.getHeaders(),
|
||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
};
|
||
|
||
const response = await fetch(queryUrl, {
|
||
method: 'POST',
|
||
headers: headers,
|
||
body: formData
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json() as AliCloudApiResponse;
|
||
|
||
if (!result.hasError) {
|
||
const content = result.content || {};
|
||
if (content.success) {
|
||
const data = content.data || {};
|
||
const apiQrStatus = data.qrCodeStatus || 'NEW';
|
||
const resultCode = data.resultCode || 0;
|
||
|
||
// 状态映射
|
||
const statusMapping: Record<string, string> = {
|
||
'NEW': 'WAITING',
|
||
'SCANED': 'SCANED',
|
||
'CONFIRMED': 'CONFIRMED',
|
||
'EXPIRED': 'EXPIRED'
|
||
};
|
||
|
||
const qrCodeStatus = statusMapping[apiQrStatus] || 'WAITING';
|
||
|
||
return {
|
||
success: true,
|
||
content: {
|
||
qrCodeStatus: qrCodeStatus,
|
||
resultCode: resultCode,
|
||
bizExt: data.bizExt || {},
|
||
data: data
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('查询二维码状态失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取访问令牌
|
||
async getAccessToken(bizExt: any): Promise<string | null> {
|
||
try {
|
||
console.log('getAccessToken - bizExt type:', typeof bizExt);
|
||
console.log('getAccessToken - bizExt:', bizExt);
|
||
|
||
// bizExt 是 Base64 编码的字符串,需要先解码
|
||
let decodedBizExt: any;
|
||
if (typeof bizExt === 'string') {
|
||
try {
|
||
const decodedString = atob(bizExt);
|
||
decodedBizExt = JSON.parse(decodedString);
|
||
console.log('getAccessToken - decoded bizExt:', JSON.stringify(decodedBizExt, null, 2));
|
||
} catch (decodeError) {
|
||
console.error('解码 bizExt 失败:', decodeError);
|
||
return null;
|
||
}
|
||
} else {
|
||
decodedBizExt = bizExt;
|
||
}
|
||
|
||
if (!decodedBizExt || !decodedBizExt.pds_login_result) {
|
||
console.log('getAccessToken - No pds_login_result found in decoded data');
|
||
return null;
|
||
}
|
||
|
||
const loginResult = decodedBizExt.pds_login_result;
|
||
console.log('getAccessToken - loginResult:', JSON.stringify(loginResult, null, 2));
|
||
this.access_token = loginResult.accessToken;
|
||
this.refresh_token = loginResult.refreshToken;
|
||
console.log('getAccessToken - access_token set:', this.access_token ? 'success' : 'failed');
|
||
console.log('getAccessToken - refresh_token set:', this.refresh_token ? 'success' : 'failed');
|
||
return this.access_token;
|
||
} catch (error) {
|
||
console.error('获取访问令牌失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取用户信息
|
||
async getUserInfo(): Promise<UserInfo | null> {
|
||
try {
|
||
if (!this.access_token) {
|
||
return null;
|
||
}
|
||
|
||
const userUrl = "https://user.aliyundrive.com/v2/user/get";
|
||
const headers = {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'Content-Type': 'application/json'
|
||
};
|
||
|
||
const response = await fetch(userUrl, {
|
||
method: 'POST',
|
||
headers: headers,
|
||
body: JSON.stringify({})
|
||
});
|
||
|
||
if (response.ok) {
|
||
return await response.json() as UserInfo;
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('获取用户信息失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取网盘信息
|
||
async getDriveInfo(): Promise<DriveInfo | null> {
|
||
try {
|
||
if (!this.access_token) {
|
||
return null;
|
||
}
|
||
|
||
const driveUrl = "https://api.aliyundrive.com/adrive/v1/user/driveCapacityDetails";
|
||
const headers = {
|
||
'Authorization': `Bearer ${this.access_token}`,
|
||
'Content-Type': 'application/json'
|
||
};
|
||
|
||
const response = await fetch(driveUrl, {
|
||
method: 'POST',
|
||
headers: headers,
|
||
body: JSON.stringify({})
|
||
});
|
||
|
||
if (response.ok) {
|
||
return await response.json() as DriveInfo;
|
||
}
|
||
return null;
|
||
} catch (error) {
|
||
console.error('获取网盘信息失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 检查是否已登录
|
||
isLoggedIn(): boolean {
|
||
return !!this.access_token;
|
||
}
|
||
|
||
// 获取访问令牌(用于返回给前端)
|
||
getToken(): string | null {
|
||
return this.access_token;
|
||
}
|
||
|
||
// 获取刷新令牌
|
||
getRefreshToken(): string | null {
|
||
return this.refresh_token;
|
||
}
|
||
}
|
||
|
||
// 会话管理接口
|
||
interface SessionData {
|
||
instance: AlipanQRLogin;
|
||
createdAt: number;
|
||
lastAccess: number;
|
||
clientFingerprint?: string;
|
||
}
|
||
|
||
// 全局实例存储 - 改为存储会话数据而不是直接存储实例
|
||
const loginSessions = new Map<string, SessionData>();
|
||
|
||
// 会话过期时间(30分钟)
|
||
const SESSION_TIMEOUT = 30 * 60 * 1000;
|
||
|
||
// 生成安全的会话ID
|
||
function generateSecureSessionId(): string {
|
||
const timestamp = Date.now().toString(36);
|
||
const randomPart = Math.random().toString(36).substring(2, 15);
|
||
const randomPart2 = Math.random().toString(36).substring(2, 15);
|
||
return `${timestamp}-${randomPart}-${randomPart2}`;
|
||
}
|
||
|
||
// 清理过期会话
|
||
function cleanupExpiredSessions() {
|
||
const now = Date.now();
|
||
for (const [sessionId, sessionData] of loginSessions.entries()) {
|
||
if (now - sessionData.lastAccess > SESSION_TIMEOUT) {
|
||
loginSessions.delete(sessionId);
|
||
console.log(`清理过期会话: ${sessionId}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 注意:不能在全局作用域使用 setInterval,改为在每次请求时检查过期会话
|
||
|
||
// 获取或创建会话
|
||
function getOrCreateSession(sessionId?: string, clientFingerprint?: string): { sessionId: string, sessionData: SessionData } {
|
||
const now = Date.now();
|
||
|
||
// 如果提供了sessionId,尝试获取现有会话
|
||
if (sessionId && loginSessions.has(sessionId)) {
|
||
const sessionData = loginSessions.get(sessionId)!;
|
||
|
||
// 检查会话是否过期
|
||
if (now - sessionData.lastAccess > SESSION_TIMEOUT) {
|
||
loginSessions.delete(sessionId);
|
||
console.log(`会话已过期,删除: ${sessionId}`);
|
||
} else {
|
||
// 更新最后访问时间
|
||
sessionData.lastAccess = now;
|
||
return { sessionId, sessionData };
|
||
}
|
||
}
|
||
|
||
// 创建新会话
|
||
const newSessionId = generateSecureSessionId();
|
||
const newSessionData: SessionData = {
|
||
instance: new AlipanQRLogin(),
|
||
createdAt: now,
|
||
lastAccess: now,
|
||
clientFingerprint
|
||
};
|
||
|
||
loginSessions.set(newSessionId, newSessionData);
|
||
console.log(`创建新会话: ${newSessionId}, 客户端指纹: ${clientFingerprint || 'none'}`);
|
||
|
||
return { sessionId: newSessionId, sessionData: newSessionData };
|
||
}
|
||
|
||
// 验证会话所有权(可选的额外安全措施)
|
||
function validateSessionOwnership(sessionId: string, clientFingerprint?: string): boolean {
|
||
const sessionData = loginSessions.get(sessionId);
|
||
if (!sessionData) return false;
|
||
|
||
// 如果设置了客户端指纹,进行验证
|
||
if (sessionData.clientFingerprint && clientFingerprint) {
|
||
return sessionData.clientFingerprint === clientFingerprint;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// 生成二维码接口
|
||
export async function generateQR(c: Context) {
|
||
try {
|
||
// 清理过期会话
|
||
cleanupExpiredSessions();
|
||
|
||
const requestedSessionId = c.req.query('session_id');
|
||
const clientFingerprint = c.req.header('X-Client-Fingerprint') || c.req.header('User-Agent');
|
||
|
||
// 获取或创建会话
|
||
const { sessionId, sessionData } = getOrCreateSession(requestedSessionId, clientFingerprint);
|
||
const alipan = sessionData.instance;
|
||
|
||
// 获取OAuth URL
|
||
const oauthUrl = await alipan.getOAuthUrl();
|
||
if (!oauthUrl) {
|
||
return c.json({error: '获取OAuth URL失败,请检查网络连接'}, 500);
|
||
}
|
||
|
||
// 获取登录页面
|
||
const loginPageResult = await alipan.getLoginPage();
|
||
if (!loginPageResult) {
|
||
return c.json({error: '获取登录页面失败,请检查网络连接'}, 500);
|
||
}
|
||
|
||
// 生成二维码
|
||
const qrData = await alipan.generateQRCode();
|
||
if (!qrData) {
|
||
return c.json({error: '生成二维码失败,可能是网络问题或API变化,请稍后重试'}, 500);
|
||
}
|
||
|
||
console.log(`会话 ${sessionId} 生成二维码成功`);
|
||
|
||
return c.json({
|
||
success: true,
|
||
session_id: sessionId,
|
||
qr_code_url: qrData.qrCodeUrl,
|
||
message: '二维码生成成功,请使用阿里云盘App扫码登录',
|
||
expires_in: SESSION_TIMEOUT / 1000 // 返回过期时间(秒)
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('生成二维码失败:', error);
|
||
return c.json({error: '生成二维码失败'}, 500);
|
||
}
|
||
}
|
||
|
||
// 检查登录状态接口
|
||
export async function checkLogin(c: Context) {
|
||
try {
|
||
const sessionId = c.req.query('session_id');
|
||
if (!sessionId) {
|
||
return c.json({error: '缺少session_id参数'}, 400);
|
||
}
|
||
|
||
const clientFingerprint = c.req.header('X-Client-Fingerprint') || c.req.header('User-Agent');
|
||
|
||
// 验证会话所有权
|
||
if (!validateSessionOwnership(sessionId, clientFingerprint)) {
|
||
return c.json({error: '会话验证失败'}, 403);
|
||
}
|
||
|
||
const sessionData = loginSessions.get(sessionId);
|
||
if (!sessionData) {
|
||
return c.json({error: '会话不存在或已过期'}, 404);
|
||
}
|
||
|
||
// 更新最后访问时间
|
||
sessionData.lastAccess = Date.now();
|
||
|
||
const alipan = sessionData.instance;
|
||
|
||
// 查询二维码状态
|
||
const statusResult = await alipan.queryQRStatus();
|
||
if (!statusResult) {
|
||
return c.json({error: '查询登录状态失败'}, 500);
|
||
}
|
||
|
||
const status = statusResult.content.qrCodeStatus;
|
||
|
||
// 如果登录成功,获取访问令牌
|
||
if (status === 'CONFIRMED') {
|
||
const accessToken = await alipan.getAccessToken(statusResult.content.bizExt);
|
||
console.log(`会话 ${sessionId} - 登录确认,token获取: ${accessToken ? '成功' : '失败'}`);
|
||
if (accessToken) {
|
||
return c.json({
|
||
success: true,
|
||
status: 'CONFIRMED',
|
||
message: '登录成功',
|
||
access_token: accessToken
|
||
});
|
||
}
|
||
}
|
||
|
||
// 状态消息映射
|
||
const statusMessages: Record<string, string> = {
|
||
'WAITING': '等待扫描',
|
||
'SCANED': '已扫描,等待确认',
|
||
'CONFIRMED': '登录成功',
|
||
'EXPIRED': '二维码已过期'
|
||
};
|
||
|
||
return c.json({
|
||
success: true,
|
||
status: status,
|
||
message: statusMessages[status] || '未知状态'
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('检查登录状态失败:', error);
|
||
return c.json({error: '检查登录状态失败'}, 500);
|
||
}
|
||
}
|
||
|
||
// 获取用户信息接口
|
||
export async function getUserInfo(c: Context) {
|
||
try {
|
||
const sessionId = c.req.query('session_id');
|
||
if (!sessionId) {
|
||
return c.json({error: '缺少session_id参数'}, 400);
|
||
}
|
||
|
||
const clientFingerprint = c.req.header('X-Client-Fingerprint') || c.req.header('User-Agent');
|
||
|
||
// 验证会话所有权
|
||
if (!validateSessionOwnership(sessionId, clientFingerprint)) {
|
||
return c.json({error: '会话验证失败'}, 403);
|
||
}
|
||
|
||
const sessionData = loginSessions.get(sessionId);
|
||
if (!sessionData) {
|
||
return c.json({error: '会话不存在或已过期'}, 404);
|
||
}
|
||
|
||
// 更新最后访问时间
|
||
sessionData.lastAccess = Date.now();
|
||
|
||
const alipan = sessionData.instance;
|
||
|
||
// 检查是否已经登录成功
|
||
console.log(`会话 ${sessionId} - 登录状态: ${alipan.isLoggedIn()}, token: ${alipan.getToken() ? '存在' : '不存在'}`);
|
||
if (!alipan.isLoggedIn()) {
|
||
return c.json({error: '用户尚未登录成功,请先完成扫码登录'}, 400);
|
||
}
|
||
|
||
// 获取用户信息
|
||
const userInfo = await alipan.getUserInfo();
|
||
if (!userInfo) {
|
||
return c.json({error: '获取用户信息失败,可能是token已过期'}, 500);
|
||
}
|
||
|
||
// 获取网盘信息
|
||
const driveInfo = await alipan.getDriveInfo();
|
||
|
||
return c.json({
|
||
success: true,
|
||
user_info: userInfo,
|
||
drive_info: driveInfo,
|
||
access_token: alipan.getToken(),
|
||
refresh_token: alipan.getRefreshToken()
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('获取用户信息失败:', error);
|
||
return c.json({error: '获取用户信息失败'}, 500);
|
||
}
|
||
}
|
||
|
||
// 退出登录接口
|
||
export async function logout(c: Context) {
|
||
try {
|
||
const sessionId = c.req.query('session_id');
|
||
if (!sessionId) {
|
||
return c.json({error: '缺少session_id参数'}, 400);
|
||
}
|
||
|
||
const clientFingerprint = c.req.header('X-Client-Fingerprint') || c.req.header('User-Agent');
|
||
|
||
// 验证会话所有权
|
||
if (!validateSessionOwnership(sessionId, clientFingerprint)) {
|
||
return c.json({error: '会话验证失败'}, 403);
|
||
}
|
||
|
||
// 删除会话
|
||
const deleted = loginSessions.delete(sessionId);
|
||
console.log(`会话 ${sessionId} 退出登录: ${deleted ? '成功' : '会话不存在'}`);
|
||
|
||
return c.json({
|
||
success: true,
|
||
message: '退出登录成功'
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('退出登录失败:', error);
|
||
return c.json({error: '退出登录失败'}, 500);
|
||
}
|
||
}
|