feat: 添加阿里云盘扫码登录v2功能 - 新增安全的多用户并发登录支持,客户端指纹验证,会话管理和自动过期机制,优化UI体验

This commit is contained in:
lansonsam 2025-06-14 18:46:18 +08:00
parent aa386512c2
commit 760a22bff7
3 changed files with 1156 additions and 1 deletions

View File

@ -91,6 +91,121 @@
[data-theme="dark"] p {
color: #ffffff;
}
/* 阿里云盘扫码v2样式 */
.qr-modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.qr-modal-content {
background-color: #fefefe;
margin: 5% auto;
padding: 20px;
border-radius: 10px;
width: 90%;
max-width: 500px;
text-align: center;
}
[data-theme="dark"] .qr-modal-content {
background-color: #2a2a3b;
color: #f0f0f0;
}
.qr-code-container {
margin: 20px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
[data-theme="dark"] .qr-code-container {
background: #1a1a2e;
}
.qr-code-img {
max-width: 200px;
max-height: 200px;
border-radius: 8px;
}
.qr-status {
margin: 15px 0;
padding: 10px;
border-radius: 5px;
font-weight: 500;
}
.qr-status.waiting {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.qr-status.scaned {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.qr-status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.qr-status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
[data-theme="dark"] .qr-status.waiting {
background: #3d3d00;
color: #ffeb3b;
border: 1px solid #ffeb3b;
}
[data-theme="dark"] .qr-status.scaned {
background: #003d4d;
color: #00bcd4;
border: 1px solid #00bcd4;
}
[data-theme="dark"] .qr-status.success {
background: #1b5e20;
color: #4caf50;
border: 1px solid #4caf50;
}
[data-theme="dark"] .qr-status.error {
background: #5d1a1a;
color: #f44336;
border: 1px solid #f44336;
}
.close-btn {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close-btn:hover {
color: black;
}
[data-theme="dark"] .close-btn:hover {
color: white;
}
</style>
</head>
<body>
@ -108,6 +223,7 @@
<option value="onedrive_us">OneDrive 美国版本</option>
<option value="onedrive_de">OneDrive 德国版本</option>
<option value="alicloud_qr">阿里网盘 扫码登录</option>
<option value="alicloud_qr2">阿里云盘 扫码登录v2</option>
<option value="baiduyun_go">百度网盘 验证登录</option>
<option value="115cloud_go">115 网盘 验证登录</option>
<option value="123cloud_go">123 网盘 直接登录</option>
@ -165,6 +281,22 @@
</p>
</div>
</div>
<!-- 阿里云盘扫码v2模态框 -->
<div id="qr-modal" class="qr-modal">
<div class="qr-modal-content">
<span class="close-btn" onclick="closeQRModal()">&times;</span>
<h4>阿里云盘扫码登录v2</h4>
<div id="qr-code-container" class="qr-code-container" style="display: none;">
<div id="qr-code-display"></div>
</div>
<div id="qr-status" class="qr-status" style="display: none;"></div>
<div class="mt-3">
<button id="refresh-qr-btn" class="btn btn-secondary" onclick="refreshQRCode()" style="display: none;">刷新二维码</button>
<button class="btn btn-secondary" onclick="closeQRModal()">关闭</button>
</div>
</div>
</div>
</div>
<script>
@ -178,7 +310,8 @@
let apps_type = document.getElementById("site-select").value;
let secret_key = document.getElementById("secret-key").value;
console.log(server_use);
if (!server_use && (apps_uuid === "" || apps_keys === "")) {
// 阿里云盘扫码登录v2不需要验证客户端ID和应用机密
if (apps_type !== "alicloud_qr2" && !server_use && (apps_uuid === "" || apps_keys === "")) {
Swal.fire({
position: 'top',
icon: 'info',
@ -188,6 +321,12 @@
});
return;
}
// 阿里云盘扫码v2直接调用专用API不需要构建传统的requests路径
if (apps_type === "alicloud_qr2") {
await startAlicloud2Login();
return;
}
let apps_subs = apps_type.split("_")[0]
let post_urls = "/" + apps_subs + "/requests?client_uid=" + apps_uuid
+ "&client_key=" + apps_keys + "&apps_types=" + apps_type
@ -232,6 +371,7 @@
+ `&client_key=${apps_keys}`;
}
}
} else Swal.fire({
icon: 'error',
title: "获取秘钥失败: " + response_data.text,
@ -303,6 +443,44 @@
// 更新输入框的值
callbackUrlInput.value = `https://api.oplist.org/${selectedValue}/callback`;
document.getElementById('secret-key-view').hidden = true;
const clientIdInput = document.getElementById('client-id');
const appSecretInput = document.getElementById('app-secret');
const serverUseCheckbox = document.getElementById('server_use');
const clientIdContainer = clientIdInput.closest('.mb-3');
const appSecretContainer = appSecretInput.closest('.mb-3');
const serverUseContainer = serverUseCheckbox.closest('.mb-3');
const callbackContainer = callbackUrlInput.closest('.mb-3');
// 阿里云盘扫码登录v2不需要客户端ID、应用机密和回调地址
if (siteSelect.value === "alicloud_qr2") {
// 隐藏整个字段容器
clientIdContainer.style.display = 'none';
appSecretContainer.style.display = 'none';
serverUseContainer.style.display = 'none';
callbackContainer.style.display = 'none';
// 清空值
clientIdInput.value = '';
appSecretInput.value = '';
serverUseCheckbox.checked = false;
} else {
// 恢复显示
clientIdContainer.style.display = 'block';
appSecretContainer.style.display = 'block';
serverUseContainer.style.display = 'block';
callbackContainer.style.display = 'block';
// 恢复正常状态
if (!serverUseCheckbox.checked) {
clientIdInput.disabled = false;
appSecretInput.disabled = false;
}
clientIdInput.placeholder = '';
appSecretInput.placeholder = '';
serverUseCheckbox.disabled = false;
}
if (siteSelect.value === "baiduyun_go") {
document.getElementById('secret-key-view').hidden = false;
}
@ -314,6 +492,7 @@
const secretKeyInput = document.getElementById('secret-key');
const server_flag = document.getElementById('server_use');
if ((siteSelect.value === "alicloud_qr"
|| siteSelect.value === "alicloud_qr2"
|| siteSelect.value === "123cloud_go"
|| siteSelect.value === "onedrive_cn"
|| siteSelect.value === "onedrive_us"
@ -376,6 +555,269 @@
})();
getToken();
// 阿里云盘扫码v2相关变量
let alicloud2SessionId = null;
let alicloud2CheckInterval = null;
let alicloud2StartTime = null;
let clientFingerprint = null;
// 生成客户端指纹
function generateClientFingerprint() {
if (clientFingerprint) return clientFingerprint;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Client fingerprint', 2, 2);
const fingerprint = [
navigator.userAgent,
navigator.language,
screen.width + 'x' + screen.height,
new Date().getTimezoneOffset(),
canvas.toDataURL(),
navigator.hardwareConcurrency || 'unknown',
navigator.deviceMemory || 'unknown'
].join('|');
// 生成简单的哈希
let hash = 0;
for (let i = 0; i < fingerprint.length; i++) {
const char = fingerprint.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
}
clientFingerprint = Math.abs(hash).toString(36);
console.log('客户端指纹生成:', clientFingerprint);
return clientFingerprint;
}
// 发送带有客户端指纹的请求
async function fetchWithFingerprint(url, options = {}) {
const fingerprint = generateClientFingerprint();
const headers = {
'X-Client-Fingerprint': fingerprint,
...options.headers
};
return fetch(url, {
...options,
headers
});
}
// 启动阿里云盘扫码v2登录
async function startAlicloud2Login() {
try {
// 显示模态框
document.getElementById('qr-modal').style.display = 'block';
setQRStatus('正在生成二维码...', 'waiting');
// 生成二维码 - 使用带指纹的请求
const response = await fetchWithFingerprint('/alicloud2/generate_qr');
const result = await response.json();
if (result.success) {
alicloud2SessionId = result.session_id;
alicloud2StartTime = Date.now();
showQRCode(result.qr_code_url);
setQRStatus('请使用阿里云盘App扫描二维码', 'waiting');
// 显示过期时间信息
if (result.expires_in) {
const expireMinutes = Math.floor(result.expires_in / 60);
console.log(`会话将在 ${expireMinutes} 分钟后过期`);
}
startStatusCheck();
} else {
setQRStatus(result.error || '生成二维码失败', 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
}
} catch (error) {
setQRStatus('网络错误,请重试', 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
console.error('生成二维码失败:', error);
}
}
// 显示二维码
function showQRCode(qrUrl) {
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrUrl)}`;
document.getElementById('qr-code-display').innerHTML = `<img src="${qrApiUrl}" alt="二维码" class="qr-code-img">`;
document.getElementById('qr-code-container').style.display = 'block';
}
// 设置状态
function setQRStatus(message, type) {
const statusEl = document.getElementById('qr-status');
statusEl.textContent = message;
statusEl.className = `qr-status ${type}`;
statusEl.style.display = 'block';
}
// 开始状态检查
function startStatusCheck() {
stopStatusCheck();
alicloud2CheckInterval = setInterval(checkAlicloud2Status, 2000);
}
// 停止状态检查
function stopStatusCheck() {
if (alicloud2CheckInterval) {
clearInterval(alicloud2CheckInterval);
alicloud2CheckInterval = null;
}
}
// 检查登录状态
async function checkAlicloud2Status() {
if (!alicloud2SessionId) return;
// 检查是否超过3分钟二维码可能过期
const elapsed = Date.now() - alicloud2StartTime;
if (elapsed > 180000) { // 3分钟
setQRStatus('二维码可能已过期,建议点击刷新重新生成', 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
stopStatusCheck();
return;
}
try {
// 使用带指纹的请求
const response = await fetchWithFingerprint(`/alicloud2/check_login?session_id=${alicloud2SessionId}`);
const result = await response.json();
if (result.success) {
switch (result.status) {
case 'WAITING':
const waitTime = Math.floor(elapsed / 1000);
setQRStatus(`等待扫描... (${waitTime}s) 请使用阿里云盘App扫码`, 'waiting');
break;
case 'SCANED':
setQRStatus('已扫描,请在手机上确认登录', 'scaned');
break;
case 'CONFIRMED':
setQRStatus('登录成功!正在获取用户信息...', 'success');
stopStatusCheck();
// 稍等一下确保token已保存
setTimeout(async () => {
await getAlicloud2UserInfo();
}, 1000);
break;
case 'EXPIRED':
setQRStatus('二维码已过期,请点击刷新重新生成', 'error');
stopStatusCheck();
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
break;
}
} else {
// 处理会话验证失败的情况
if (response.status === 403) {
setQRStatus('会话验证失败,请重新生成二维码', 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
stopStatusCheck();
} else {
setQRStatus('检查状态失败: ' + (result.error || '未知错误'), 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
}
}
} catch (error) {
console.error('检查登录状态失败:', error);
setQRStatus('网络连接失败,请检查网络后重试', 'error');
document.getElementById('refresh-qr-btn').style.display = 'inline-block';
}
}
// 获取用户信息
async function getAlicloud2UserInfo() {
if (!alicloud2SessionId) return;
try {
// 使用带指纹的请求
const response = await fetchWithFingerprint(`/alicloud2/get_user_info?session_id=${alicloud2SessionId}`);
const result = await response.json();
if (result.success && result.user_info) {
// 关闭模态框
closeQRModal();
// 显示成功消息
await Swal.fire({
position: 'top',
icon: 'success',
title: '登录成功',
html: `<div>用户: ${result.user_info.nick_name || result.user_info.user_id}</div>`,
showConfirmButton: true
});
// 填充token字段使用真实的tokens
if (result.access_token) {
document.getElementById("access-token").value = result.access_token;
}
if (result.refresh_token) {
document.getElementById("refresh-token").value = result.refresh_token;
}
// 清理会话
await fetchWithFingerprint(`/alicloud2/logout?session_id=${alicloud2SessionId}`);
alicloud2SessionId = null;
} else {
// 处理会话验证失败的情况
if (response.status === 403) {
setQRStatus('会话验证失败,请重新登录', 'error');
} else {
setQRStatus('获取用户信息失败: ' + (result.error || '未知错误'), 'error');
}
}
} catch (error) {
setQRStatus('获取用户信息失败', 'error');
console.error('获取用户信息失败:', error);
}
}
// 刷新二维码
async function refreshQRCode() {
document.getElementById('refresh-qr-btn').style.display = 'none';
// 清理旧会话
if (alicloud2SessionId) {
try {
await fetchWithFingerprint(`/alicloud2/logout?session_id=${alicloud2SessionId}`);
} catch (e) {
console.log('清理旧会话失败:', e);
}
alicloud2SessionId = null;
}
await startAlicloud2Login();
}
// 关闭模态框
function closeQRModal() {
document.getElementById('qr-modal').style.display = 'none';
stopStatusCheck();
// 清理会话
if (alicloud2SessionId) {
fetchWithFingerprint(`/alicloud2/logout?session_id=${alicloud2SessionId}`);
alicloud2SessionId = null;
}
// 重置界面
document.getElementById('qr-code-container').style.display = 'none';
document.getElementById('qr-status').style.display = 'none';
document.getElementById('refresh-qr-btn').style.display = 'none';
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById('qr-modal');
if (event.target === modal) {
closeQRModal();
}
}
</script>
</body>
</html>

692
src/aliui2.ts Normal file
View File

@ -0,0 +1,692 @@
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);
}
}

View File

@ -5,6 +5,7 @@ import manifest from '__STATIC_CONTENT_MANIFEST'
import * as local from "hono/cookie";
import * as oneui from './oneui';
import * as aliui from './aliui';
import * as aliui2 from './aliui2';
import * as ui115 from './115ui';
import * as ui123 from './123ui';
import * as baidu from './baidu';
@ -40,6 +41,26 @@ app.get('/alicloud/callback', async (c: Context) => {
return aliui.alyToken(c);
});
// 阿里云盘扫码2 - 生成二维码 ##############################################################################
app.get('/alicloud2/generate_qr', async (c: Context) => {
return aliui2.generateQR(c);
});
// 阿里云盘扫码2 - 检查登录状态 ##############################################################################
app.get('/alicloud2/check_login', async (c: Context) => {
return aliui2.checkLogin(c);
});
// 阿里云盘扫码2 - 获取用户信息 ##############################################################################
app.get('/alicloud2/get_user_info', async (c: Context) => {
return aliui2.getUserInfo(c);
});
// 阿里云盘扫码2 - 退出登录 ##############################################################################
app.get('/alicloud2/logout', async (c: Context) => {
return aliui2.logout(c);
});
// 登录申请 ##############################################################################
app.get('/baiduyun/requests', async (c: Context) => {
return baidu.oneLogin(c);