From b559ed803384b227e7c3633195e1ee4dd33ae6fc Mon Sep 17 00:00:00 2001 From: roc-ld <67948462+roc-ld@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:46:13 +1100 Subject: [PATCH 1/2] Fix server-side Douban proxy resolution to respect NEXT_PUBLIC_DOUBAN_PROXY_TYPE and reuse the same proxy logic as the client. --- src/app/api/douban/categories/route.ts | 6 ++-- src/app/api/douban/recommends/route.ts | 7 ++-- src/app/api/douban/route.ts | 10 ++++-- src/lib/douban.ts | 47 ++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/app/api/douban/categories/route.ts b/src/app/api/douban/categories/route.ts index 3af8b6f035..7121cd92f1 100644 --- a/src/app/api/douban/categories/route.ts +++ b/src/app/api/douban/categories/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { getCacheTime } from '@/lib/config'; -import { fetchDoubanData } from '@/lib/douban'; +import { fetchDoubanData, resolveDoubanUrl } from '@/lib/douban'; import { DoubanItem, DoubanResult } from '@/lib/types'; interface DoubanCategoryApiResponse { @@ -61,7 +61,9 @@ export async function GET(request: Request) { ); } - const target = `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}`; + const target = resolveDoubanUrl( + `https://m.douban.com/rexxar/api/v2/subject/recent_hot/${kind}?start=${pageStart}&limit=${pageLimit}&category=${category}&type=${type}` + ); try { // 调用豆瓣 API diff --git a/src/app/api/douban/recommends/route.ts b/src/app/api/douban/recommends/route.ts index 34f16c5ab4..659972213d 100644 --- a/src/app/api/douban/recommends/route.ts +++ b/src/app/api/douban/recommends/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getCacheTime } from '@/lib/config'; -import { fetchDoubanData } from '@/lib/douban'; +import { fetchDoubanData, resolveDoubanUrl } from '@/lib/douban'; import { DoubanResult } from '@/lib/types'; interface DoubanRecommendApiResponse { @@ -78,7 +78,9 @@ export async function GET(request: NextRequest) { tags.push(platform); } - const baseUrl = `https://m.douban.com/rexxar/api/v2/${kind}/recommend`; + const baseUrl = resolveDoubanUrl( + `https://m.douban.com/rexxar/api/v2/${kind}/recommend` + ); const params = new URLSearchParams(); params.append('refresh', '0'); params.append('start', pageStart.toString()); @@ -92,7 +94,6 @@ export async function GET(request: NextRequest) { } const target = `${baseUrl}?${params.toString()}`; - console.log(target); try { const doubanData = await fetchDoubanData( target diff --git a/src/app/api/douban/route.ts b/src/app/api/douban/route.ts index 8cf24fb5f9..fb2a410faf 100644 --- a/src/app/api/douban/route.ts +++ b/src/app/api/douban/route.ts @@ -57,7 +57,9 @@ export async function GET(request: Request) { return handleTop250(pageStart); } - const target = `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageSize}&page_start=${pageStart}`; + const target = resolveDoubanUrl( + `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageSize}&page_start=${pageStart}` + ); try { // 调用豆瓣 API @@ -96,7 +98,9 @@ export async function GET(request: Request) { } function handleTop250(pageStart: number) { - const target = `https://movie.douban.com/top250?start=${pageStart}&filter=`; + const target = resolveDoubanUrl( + `https://movie.douban.com/top250?start=${pageStart}&filter=` + ); // 直接使用 fetch 获取 HTML 页面 const controller = new AbortController(); @@ -126,7 +130,7 @@ function handleTop250(pageStart: number) { // 通过正则同时捕获影片 id、标题、封面以及评分 const moviePattern = - /
[\s\S]*?]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*?]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]*)<\/span>[\s\S]*?<\/div>/g; + /
[\s\S]*?]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*?]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]*)<\/spa/; const movies: DoubanItem[] = []; let match; diff --git a/src/lib/douban.ts b/src/lib/douban.ts index 3d33492c53..8f6335d5ed 100644 --- a/src/lib/douban.ts +++ b/src/lib/douban.ts @@ -3,6 +3,53 @@ * @param url 请求的URL * @returns Promise 返回指定类型的数据 */ + +export type DoubanProxyType = + | 'direct' + | 'cors-proxy-zwei' + | 'cmliussss-cdn-tencent' + | 'cmliussss-cdn-ali' + | 'cors-anywhere' + | 'custom'; + +export function getDoubanProxyConfig(): { + proxyType: DoubanProxyType; + proxyUrl: string; +} { + const proxyType = + (process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'cmliussss-cdn-tencent') as DoubanProxyType; + const proxyUrl = process.env.NEXT_PUBLIC_DOUBAN_PROXY || ''; + + return { + proxyType, + proxyUrl, + }; +} + +export function resolveDoubanUrl(url: string): string { + const { proxyType, proxyUrl } = getDoubanProxyConfig(); + + switch (proxyType) { + case 'cors-proxy-zwei': + return `https://ciao-cors.is-an.org/${encodeURIComponent(url)}`; + case 'cmliussss-cdn-tencent': + return url + .replace('https://m.douban.com', 'https://m.douban.cmliussss.net') + .replace('https://movie.douban.com', 'https://movie.douban.cmliussss.net'); + case 'cmliussss-cdn-ali': + return url + .replace('https://m.douban.com', 'https://m.douban.cmliussss.com') + .replace('https://movie.douban.com', 'https://movie.douban.cmliussss.com'); + case 'cors-anywhere': + return `https://cors-anywhere.com/${url}`; + case 'custom': + return proxyUrl ? `${proxyUrl}${encodeURIComponent(url)}` : url; + case 'direct': + default: + return url; + } +} + export async function fetchDoubanData(url: string): Promise { // 添加超时控制 const controller = new AbortController(); From 5389cd099e4a2dc407feae1ccc837fe98d294be8 Mon Sep 17 00:00:00 2001 From: roc-ld <67948462+roc-ld@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:55:37 +1100 Subject: [PATCH 2/2] Fix malformed Top250 regex and keep proxy fix in place. --- src/app/api/douban/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/api/douban/route.ts b/src/app/api/douban/route.ts index fb2a410faf..53e48ad704 100644 --- a/src/app/api/douban/route.ts +++ b/src/app/api/douban/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from 'next/server'; import { getCacheTime } from '@/lib/config'; -import { fetchDoubanData } from '@/lib/douban'; +import { fetchDoubanData, resolveDoubanUrl } from '@/lib/douban'; import { DoubanItem, DoubanResult } from '@/lib/types'; interface DoubanApiResponse { @@ -130,7 +130,7 @@ function handleTop250(pageStart: number) { // 通过正则同时捕获影片 id、标题、封面以及评分 const moviePattern = - /
[\s\S]*?]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*?]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]*)<\/spa/; + /
[\s\S]*?]+href="https?:\/\/movie\.douban\.com\/subject\/(\d+)\/"[\s\S]*?]+alt="([^"]+)"[^>]*src="([^"]+)"[\s\S]*?]*>([^<]*)<\/span>[\s\S]*?<\/div>/g; const movies: DoubanItem[] = []; let match;