feat: 添加 CORS 支持,处理预检请求并更新 API 响应头
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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).*)',
|
||||
],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user