feat: 添加 CORS 支持,处理预检请求并更新 API 响应头

This commit is contained in:
katelya
2025-09-02 17:43:06 +08:00
parent c69e9a380f
commit 1e3467fff2
9 changed files with 132 additions and 23 deletions
+16 -5
View File
@@ -1,21 +1,29 @@
import { NextResponse } from 'next/server';
import { getAvailableApiSites, getCacheTime } from '@/lib/config';
import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors';
import { getDetailFromApi } from '@/lib/downstream';
export const runtime = 'edge';
// 处理OPTIONS预检请求(OrionTV客户端需要)
export async function OPTIONS() {
return handleOptionsRequest();
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const sourceCode = searchParams.get('source');
if (!id || !sourceCode) {
return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
const response = NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
return addCorsHeaders(response);
}
if (!/^[\w-]+$/.test(id)) {
return NextResponse.json({ error: '无效的视频ID格式' }, { status: 400 });
const response = NextResponse.json({ error: '无效的视频ID格式' }, { status: 400 });
return addCorsHeaders(response);
}
try {
@@ -23,23 +31,26 @@ export async function GET(request: Request) {
const apiSite = apiSites.find((site) => site.key === sourceCode);
if (!apiSite) {
return NextResponse.json({ error: '无效的API来源' }, { status: 400 });
const response = NextResponse.json({ error: '无效的API来源' }, { status: 400 });
return addCorsHeaders(response);
}
const result = await getDetailFromApi(apiSite, id);
const cacheTime = await getCacheTime();
return NextResponse.json(result, {
const response = NextResponse.json(result, {
headers: {
'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
},
});
return addCorsHeaders(response);
} catch (error) {
return NextResponse.json(
const response = NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
return addCorsHeaders(response);
}
}
+17 -5
View File
@@ -1,14 +1,22 @@
import { NextResponse } from 'next/server';
import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors';
export const runtime = 'edge';
// 处理OPTIONS预检请求(OrionTV客户端需要)
export async function OPTIONS() {
return handleOptionsRequest();
}
// OrionTV 兼容接口
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const imageUrl = searchParams.get('url');
if (!imageUrl) {
return NextResponse.json({ error: 'Missing image URL' }, { status: 400 });
const response = NextResponse.json({ error: 'Missing image URL' }, { status: 400 });
return addCorsHeaders(response);
}
try {
@@ -21,19 +29,21 @@ export async function GET(request: Request) {
});
if (!imageResponse.ok) {
return NextResponse.json(
const response = NextResponse.json(
{ error: imageResponse.statusText },
{ status: imageResponse.status }
);
return addCorsHeaders(response);
}
const contentType = imageResponse.headers.get('content-type');
if (!imageResponse.body) {
return NextResponse.json(
const response = NextResponse.json(
{ error: 'Image response has no body' },
{ status: 500 }
);
return addCorsHeaders(response);
}
// 创建响应头
@@ -48,14 +58,16 @@ export async function GET(request: Request) {
headers.set('Vercel-CDN-Cache-Control', 'public, s-maxage=15720000');
// 直接返回图片流
return new Response(imageResponse.body, {
const response = new Response(imageResponse.body, {
status: 200,
headers,
});
return addCorsHeaders(response);
} catch (error) {
return NextResponse.json(
const response = NextResponse.json(
{ error: 'Error fetching image' },
{ status: 500 }
);
return addCorsHeaders(response);
}
}
+16 -5
View File
@@ -1,10 +1,16 @@
import { NextResponse } from 'next/server';
import { getAvailableApiSites, getCacheTime } from '@/lib/config';
import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors';
import { searchFromApi } from '@/lib/downstream';
export const runtime = 'edge';
// 处理OPTIONS预检请求(OrionTV客户端需要)
export async function OPTIONS() {
return handleOptionsRequest();
}
// OrionTV 兼容接口
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
@@ -13,7 +19,7 @@ export async function GET(request: Request) {
if (!query || !resourceId) {
const cacheTime = await getCacheTime();
return NextResponse.json(
const response = NextResponse.json(
{ result: null, error: '缺少必要参数: q 或 resourceId' },
{
headers: {
@@ -23,6 +29,7 @@ export async function GET(request: Request) {
},
}
);
return addCorsHeaders(response);
}
const apiSites = await getAvailableApiSites();
@@ -31,13 +38,14 @@ export async function GET(request: Request) {
// 根据 resourceId 查找对应的 API 站点
const targetSite = apiSites.find((site) => site.key === resourceId);
if (!targetSite) {
return NextResponse.json(
const response = NextResponse.json(
{
error: `未找到指定的视频源: ${resourceId}`,
result: null,
},
{ status: 404 }
);
return addCorsHeaders(response);
}
const results = await searchFromApi(targetSite, query);
@@ -45,15 +53,16 @@ export async function GET(request: Request) {
const cacheTime = await getCacheTime();
if (result.length === 0) {
return NextResponse.json(
const response = NextResponse.json(
{
error: '未找到结果',
result: null,
},
{ status: 404 }
);
return addCorsHeaders(response);
} else {
return NextResponse.json(
const response = NextResponse.json(
{ results: result },
{
headers: {
@@ -63,14 +72,16 @@ export async function GET(request: Request) {
},
}
);
return addCorsHeaders(response);
}
} catch (error) {
return NextResponse.json(
const response = NextResponse.json(
{
error: '搜索失败',
result: null,
},
{ status: 500 }
);
return addCorsHeaders(response);
}
}
+10 -2
View File
@@ -1,23 +1,31 @@
import { NextResponse } from 'next/server';
import { getAvailableApiSites, getCacheTime } from '@/lib/config';
import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors';
export const runtime = 'edge';
// 处理OPTIONS预检请求(OrionTV客户端需要)
export async function OPTIONS() {
return handleOptionsRequest();
}
// OrionTV 兼容接口
export async function GET() {
try {
const apiSites = await getAvailableApiSites();
const cacheTime = await getCacheTime();
return NextResponse.json(apiSites, {
const response = NextResponse.json(apiSites, {
headers: {
'Cache-Control': `public, max-age=${cacheTime}, s-maxage=${cacheTime}`,
'CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
'Vercel-CDN-Cache-Control': `public, s-maxage=${cacheTime}`,
},
});
return addCorsHeaders(response);
} catch (error) {
return NextResponse.json({ error: '获取资源失败' }, { status: 500 });
const response = NextResponse.json({ error: '获取资源失败' }, { status: 500 });
return addCorsHeaders(response);
}
}
+12 -3
View File
@@ -1,17 +1,23 @@
import { NextResponse } from 'next/server';
import { getAvailableApiSites, getCacheTime } from '@/lib/config';
import { addCorsHeaders, handleOptionsRequest } from '@/lib/cors';
import { searchFromApi } from '@/lib/downstream';
export const runtime = 'edge';
// 处理OPTIONS预检请求(OrionTV客户端需要)
export async function OPTIONS() {
return handleOptionsRequest();
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q');
if (!query) {
const cacheTime = await getCacheTime();
return NextResponse.json(
const response = NextResponse.json(
{ results: [] },
{
headers: {
@@ -21,6 +27,7 @@ export async function GET(request: Request) {
},
}
);
return addCorsHeaders(response);
}
const apiSites = await getAvailableApiSites();
@@ -31,7 +38,7 @@ export async function GET(request: Request) {
const flattenedResults = results.flat();
const cacheTime = await getCacheTime();
return NextResponse.json(
const response = NextResponse.json(
{ results: flattenedResults },
{
headers: {
@@ -41,7 +48,9 @@ export async function GET(request: Request) {
},
}
);
return addCorsHeaders(response);
} catch (error) {
return NextResponse.json({ error: '搜索失败' }, { status: 500 });
const response = NextResponse.json({ error: '搜索失败' }, { status: 500 });
return addCorsHeaders(response);
}
}
+32
View File
@@ -0,0 +1,32 @@
// CORS工具函数,用于为OrionTV客户端提供跨域支持
export function createCorsHeaders(): Headers {
const headers = new Headers();
// 设置CORS头部,允许OrionTV客户端跨域访问
headers.set('Access-Control-Allow-Origin', '*');
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
headers.set('Access-Control-Max-Age', '86400'); // 24小时
return headers;
}
// 为NextResponse添加CORS头部
export function addCorsHeaders(response: Response): Response {
const corsHeaders = createCorsHeaders();
// 将CORS头部添加到现有响应头部中
corsHeaders.forEach((value, key) => {
response.headers.set(key, value);
});
return response;
}
// 处理OPTIONS预检请求
export function handleOptionsRequest(): Response {
return new Response(null, {
status: 200,
headers: createCorsHeaders(),
});
}
+1 -1
View File
@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
// 配置middleware匹配规则
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config).*)',
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/search|api/detail|api/image-proxy).*)',
],
};