feat: 添加音乐API代理以解决CORS跨域问题

- index.html: 将API_BASE改为相对路径 /api/music-api
- sync-server/server.js: 新增 /music-api 代理端点转发请求至 music-dl.sayqz.com
- 新增 proxyRequest 函数处理HTTPS代理请求和重定向
This commit is contained in:
史悦
2026-01-14 15:17:58 +08:00
parent c9fac4b7fe
commit a76ef33c4c
2 changed files with 82 additions and 24 deletions

View File

@@ -138,7 +138,7 @@
<script type="text/babel">
const { useState, useEffect, useRef, useMemo, useCallback } = React;
const API_BASE = "https://music-dl.sayqz.com";
const API_BASE = "/api/music-api";
// Use relative path for sync service, assuming Nginx proxy is configured to forward /api/kv to the sync service
const SYNC_API_BASE = "/api";

View File

@@ -17,29 +17,39 @@ if (!fs.existsSync(MUSIC_DIR)) {
fs.mkdirSync(MUSIC_DIR, { recursive: true });
}
const server = http.createServer((req, res) => {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
// Path format: /kv/:key?token=...
const match = pathname.match(/^\/kv\/([a-zA-Z0-9_-]+)$/);
if (!match) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid path' }));
return;
}
const server = http.createServer((req, res) => {
// CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
// --- Music API Proxy ---
// Proxy requests to music-dl.sayqz.com to avoid CORS issues
if (pathname === '/music-api' || pathname === '/music-api/') {
const queryString = parsedUrl.search || '';
const targetUrl = `https://music-dl.sayqz.com/api/${queryString}`;
proxyRequest(targetUrl, req, res);
return;
}
// Path format: /kv/:key?token=...
const match = pathname.match(/^\/kv\/([a-zA-Z0-9_-]+)$/);
if (!match) {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid path' }));
return;
}
const key = match[1];
const token = parsedUrl.query.token;
@@ -298,6 +308,54 @@ function downloadFile(url, dest) {
});
}
// --- Music API Proxy Function ---
function proxyRequest(targetUrl, req, res) {
const parsedTarget = url.parse(targetUrl);
const options = {
hostname: parsedTarget.hostname,
port: 443,
path: parsedTarget.path,
method: req.method,
headers: {
'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': '*/*',
'Accept-Encoding': 'identity'
}
};
const proxyReq = https.request(options, (proxyRes) => {
// Handle redirects
if (proxyRes.statusCode >= 300 && proxyRes.statusCode < 400 && proxyRes.headers.location) {
// For redirects, return the redirect URL to client
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ url: proxyRes.headers.location }));
return;
}
// Copy response headers (except problematic ones)
const headers = { ...proxyRes.headers };
delete headers['content-encoding'];
delete headers['transfer-encoding'];
res.writeHead(proxyRes.statusCode, headers);
proxyRes.pipe(res);
});
proxyReq.on('error', (e) => {
console.error('[Proxy] Request error:', e.message);
res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Proxy request failed', message: e.message }));
});
// Forward request body for POST requests
if (req.method === 'POST') {
req.pipe(proxyReq);
} else {
proxyReq.end();
}
}
function writeMetadata(inputPath, outputPath, metadata) {
return new Promise((resolve, reject) => {
const args = ['-i', inputPath];