Skip to content
This repository was archived by the owner on Jul 3, 2026. It is now read-only.

Commit 5cafdec

Browse files
committed
2.1.2
【优化】 - 更改代理策略:下载与 Raw content 预览直连 proxy
1 parent cfce7b9 commit 5cafdec

7 files changed

Lines changed: 118 additions & 98 deletions

File tree

src/hooks/useDownload.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
type FolderDownloadEntry,
2121
type ZipOutputSink,
2222
} from "@/utils/download/folderZipPipeline";
23-
import { getForceServerProxy } from "@/services/github/config/ProxyForceManager";
23+
import { ProxyUrlTransformer } from "@/services/github/proxy";
2424
import { useI18n } from "@/contexts/I18nContext";
2525

2626
/** 下载状态初始值 */
@@ -112,6 +112,14 @@ export const useDownload = (
112112

113113
const hasBeenCancelled = () => isCancelledRef.current;
114114

115+
const getDirectProxyDownloadUrl = (sourceUrl: string): string => {
116+
const proxyService = GitHub.Proxy.getCurrentProxyService();
117+
if (proxyService.trim() === "") {
118+
return sourceUrl;
119+
}
120+
return ProxyUrlTransformer.applyProxyToUrl(sourceUrl, proxyService);
121+
};
122+
115123
// 取消下载
116124
const cancelDownload = () => {
117125
if (!isDownloading) {
@@ -152,26 +160,13 @@ export const useDownload = (
152160

153161
isCancelledRef.current = false;
154162
dispatch({ type: "SET_DOWNLOADING_FILE", path: item.path });
155-
156-
// 创建新的AbortController
157163
abortControllerRef.current = new AbortController();
158164
const signal = abortControllerRef.current.signal;
159165

160166
try {
161-
// 使用代理URL获取文件
162-
let downloadUrl = item.download_url;
163-
164-
// 如果是非开发环境或启用了令牌模式,使用服务端API代理
165-
if (getForceServerProxy()) {
166-
downloadUrl = GitHub.Content.getServerRepoFileProxyUrl(
167-
item.path,
168-
GitHub.Branch.getCurrentBranch(),
169-
);
170-
}
171-
167+
const downloadUrl = getDirectProxyDownloadUrl(item.download_url);
172168
const response = await fetch(downloadUrl, { signal });
173169

174-
// 检查是否已取消 (ref可在异步期间被cancelDownload修改)
175170
if (hasBeenCancelled()) {
176171
logger.info("文件下载已取消");
177172
return;
@@ -186,14 +181,12 @@ export const useDownload = (
186181

187182
const blob = await response.blob();
188183

189-
// 再次检查是否已取消 (ref可在异步期间被cancelDownload修改)
190184
if (hasBeenCancelled()) {
191185
logger.info("文件下载已取消");
192186
return;
193187
}
194188

195189
saveAs(blob, item.name);
196-
197190
logger.info(`文件下载成功: ${item.path}`);
198191
} catch (e: unknown) {
199192
const error = e as Error;
@@ -243,14 +236,7 @@ export const useDownload = (
243236
continue;
244237
}
245238

246-
// 如果是非开发环境或启用了令牌模式,使用服务端API代理
247-
let downloadUrl = item.download_url;
248-
if (getForceServerProxy()) {
249-
downloadUrl = GitHub.Content.getServerRepoFileProxyUrl(
250-
item.path,
251-
GitHub.Branch.getCurrentBranch(),
252-
);
253-
}
239+
const downloadUrl = getDirectProxyDownloadUrl(item.download_url);
254240

255241
fileList.push({
256242
path: relativePath,

src/hooks/useFilePreview.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
updateUrlWithHistory,
2121
hasPreviewParam,
2222
} from "@/utils/routing/urlManager";
23-
import { getForceServerProxy } from "@/services/github/config/ProxyForceManager";
2423
import { useI18n } from "@/contexts/I18nContext";
2524

2625
/** 预览状态初始值 */
@@ -206,11 +205,9 @@ export const useFilePreview = (
206205
dispatch({ type: "RESET_PREVIEW" });
207206

208207
try {
209-
const currentBranch = GitHub.Branch.getCurrentBranch();
210-
const proxyUrl = getForceServerProxy()
211-
? GitHub.Content.getServerRepoFileProxyUrl(item.path, currentBranch)
212-
: (GitHub.Proxy.transformImageUrl(item.download_url, item.path, useTokenMode) ??
213-
item.download_url);
208+
const proxyUrl =
209+
GitHub.Proxy.transformImageUrl(item.download_url, item.path, useTokenMode) ??
210+
item.download_url;
214211

215212
const fileNameLower = item.name.toLowerCase();
216213
const isCurrentTarget = (): boolean => currentPreviewItemRef.current?.path === targetPath;
@@ -286,10 +283,7 @@ export const useFilePreview = (
286283
try {
287284
await pdf.openPDFPreview({
288285
fileName: item.name,
289-
downloadUrl: item.download_url,
290-
serverProxyUrl: getForceServerProxy()
291-
? GitHub.Content.getServerRepoFileProxyUrl(item.path, currentBranch)
292-
: undefined,
286+
downloadUrl: proxyUrl,
293287
theme: muiTheme,
294288
translations: {
295289
loading: t("ui.pdf.loading"),

src/services/github/core/content/service.test.ts

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
22

33
import { createAbortError } from "@/utils/network/abort";
44

5-
const { axiosGetMock } = vi.hoisted(() => ({
5+
const { axiosGetMock, getCurrentProxyServiceMock, applyProxyToUrlMock } = vi.hoisted(() => ({
66
axiosGetMock: vi.fn(),
7+
getCurrentProxyServiceMock: vi.fn(() => "https://proxy.example.com"),
8+
applyProxyToUrlMock: vi.fn(
9+
(url: string, proxyUrl: string) => `${proxyUrl}/${url.replace(/^https?:\/\//u, "")}`,
10+
),
711
}));
812

913
vi.mock("axios", () => ({
@@ -43,6 +47,13 @@ vi.mock("../../schemas", () => ({
4347
validateGitHubContentsArray: vi.fn(() => ({ isValid: true, invalidItems: [] })),
4448
}));
4549

50+
vi.mock("../../proxy", () => ({
51+
getCurrentProxyService: getCurrentProxyServiceMock,
52+
ProxyUrlTransformer: {
53+
applyProxyToUrl: applyProxyToUrlMock,
54+
},
55+
}));
56+
4657
vi.mock("./cacheState", () => ({
4758
ensureCacheInitialized: vi.fn(async () => {}),
4859
getCachedDirectoryContents: vi.fn(async () => null),
@@ -67,7 +78,7 @@ if (typeof window === "undefined") {
6778
vi.stubGlobal("window", globalThis);
6879
}
6980

70-
import { shouldUseServerAPI } from "../../config";
81+
import { getForceServerProxy, shouldUseServerAPI } from "../../config";
7182
const { clearBatcherCache, getContents, getFileContent } = await import("./service");
7283

7384
describe("content service abort handling", () => {
@@ -77,6 +88,11 @@ describe("content service abort handling", () => {
7788
vi.stubGlobal("window", globalThis);
7889
clearBatcherCache();
7990
vi.mocked(shouldUseServerAPI).mockReturnValue(false);
91+
vi.mocked(getForceServerProxy).mockReturnValue(false);
92+
getCurrentProxyServiceMock.mockReturnValue("https://proxy.example.com");
93+
applyProxyToUrlMock.mockImplementation(
94+
(url: string, proxyUrl: string) => `${proxyUrl}/${url.replace(/^https?:\/\//u, "")}`,
95+
);
8096
});
8197

8298
it("propagates abort to direct fetch requests", async () => {
@@ -176,4 +192,56 @@ describe("content service abort handling", () => {
176192
await expect(promise).rejects.toMatchObject({ name: "AbortError" });
177193
expect(fetchSignal?.aborted).toBe(true);
178194
});
195+
196+
it("prefers direct proxy URL for file content requests", async () => {
197+
const fetchMock = vi.fn(async () => ({
198+
ok: true,
199+
status: 200,
200+
statusText: "OK",
201+
text: async () => "proxy content",
202+
}));
203+
vi.stubGlobal("fetch", fetchMock);
204+
205+
const content = await getFileContent(
206+
"https://raw.githubusercontent.com/test-owner/test-repo/main/docs/readme.md",
207+
);
208+
209+
expect(content).toBe("proxy content");
210+
expect(fetchMock).toHaveBeenCalledTimes(1);
211+
expect(fetchMock.mock.calls[0]?.[0]).toBe(
212+
"https://proxy.example.com/raw.githubusercontent.com/test-owner/test-repo/main/docs/readme.md",
213+
);
214+
});
215+
216+
it("falls back to server API when direct proxy request fails in force mode", async () => {
217+
vi.mocked(getForceServerProxy).mockReturnValue(true);
218+
const fetchMock = vi
219+
.fn()
220+
.mockResolvedValueOnce({
221+
ok: false,
222+
status: 502,
223+
statusText: "Bad Gateway",
224+
text: async () => "",
225+
})
226+
.mockResolvedValueOnce({
227+
ok: true,
228+
status: 200,
229+
statusText: "OK",
230+
text: async () => "fallback content",
231+
});
232+
vi.stubGlobal("fetch", fetchMock);
233+
234+
const content = await getFileContent(
235+
"https://raw.githubusercontent.com/test-owner/test-repo/main/docs/readme.md",
236+
);
237+
238+
expect(content).toBe("fallback content");
239+
expect(fetchMock).toHaveBeenCalledTimes(2);
240+
expect(fetchMock.mock.calls[0]?.[0]).toBe(
241+
"https://proxy.example.com/raw.githubusercontent.com/test-owner/test-repo/main/docs/readme.md",
242+
);
243+
expect(String(fetchMock.mock.calls[1]?.[0] ?? "")).toContain(
244+
"/api/github?action=getGitHubAsset&url=",
245+
);
246+
});
179247
});

src/services/github/core/content/service.ts

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
validateGitHubContentsArray,
1414
} from "../../schemas";
1515
import { getAuthHeaders } from "../Auth";
16-
import { USE_TOKEN_MODE, getApiUrl, getCurrentBranch } from "../Config";
16+
import { getApiUrl, getCurrentBranch } from "../Config";
17+
import { getCurrentProxyService, ProxyUrlTransformer } from "../../proxy";
1718
import {
1819
ensureCacheInitialized,
1920
getCachedDirectoryContents,
@@ -198,33 +199,37 @@ export async function getFileContent(fileUrl: string, signal?: AbortSignal): Pro
198199
}
199200

200201
try {
201-
const response = await (async () => {
202-
if (getForceServerProxy()) {
203-
const serverApiUrl = buildServerApiUrlForGitHubResource(fileUrl, branch);
204-
return fetch(serverApiUrl, {
205-
signal,
206-
});
202+
const fetchTextByUrl = async (targetUrl: string): Promise<string> => {
203+
const response = await fetch(targetUrl, { signal });
204+
if (!response.ok) {
205+
throw new Error(`HTTP ${response.status.toString()}: ${response.statusText}`);
207206
}
208-
209-
let proxyUrl: string;
210-
if (fileUrl.includes("raw.githubusercontent.com")) {
211-
proxyUrl = fileUrl.replace("https://raw.githubusercontent.com", "/github-raw");
212-
} else {
213-
proxyUrl = fileUrl;
207+
return response.text();
208+
};
209+
210+
const currentProxyService = getCurrentProxyService().trim();
211+
const directProxyUrl =
212+
currentProxyService === ""
213+
? fileUrl
214+
: ProxyUrlTransformer.applyProxyToUrl(fileUrl, currentProxyService);
215+
216+
let content: string;
217+
try {
218+
content = await fetchTextByUrl(directProxyUrl);
219+
} catch (directError) {
220+
if (isAbortError(directError)) {
221+
throw createAbortError("Request aborted");
214222
}
215223

216-
return fetch(proxyUrl, {
217-
headers: USE_TOKEN_MODE ? getAuthHeaders() : {},
218-
signal,
219-
});
220-
})();
224+
if (!getForceServerProxy()) {
225+
throw directError;
226+
}
221227

222-
if (!response.ok) {
223-
throw new Error(`HTTP ${response.status.toString()}: ${response.statusText}`);
228+
const serverApiUrl = buildServerApiUrlForGitHubResource(fileUrl, branch);
229+
logger.warn(`直连代理获取文件失败,回退服务端API: ${directProxyUrl}`);
230+
content = await fetchTextByUrl(serverApiUrl);
224231
}
225232

226-
const content = await response.text();
227-
228233
await storeFileContent(cacheKey, fileUrl, content);
229234

230235
return content;

src/services/github/proxy/ProxyService.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { logger } from "@/utils";
22
import { getProxyConfig, getRuntimeConfig } from "@/config";
33
import { USE_TOKEN_MODE, PROXY_SERVICES } from "./ProxyConfig";
4-
import { getForceServerProxy } from "../config";
54
import { proxyHealthManager } from "./ProxyHealthManager";
65
import { ProxyUrlTransformer } from "./ProxyUrlTransformer";
7-
import { buildServerApiUrlForGitHubResource } from "../core/content/serverApiUrls";
86

97
const proxyConfig = getProxyConfig();
108
const runtimeConfig = getRuntimeConfig();
@@ -43,17 +41,12 @@ export async function getProxiedUrl(
4341
return url;
4442
}
4543

46-
// 优先使用服务端API代理
47-
if (getForceServerProxy()) {
48-
return buildServerApiUrlForGitHubResource(url);
49-
}
50-
5144
// 使用智能代理选择
5245
const bestProxy = proxyHealthManager.getBestProxy();
5346

5447
if (bestProxy === "") {
55-
logger.warn("没有可用代理,使用服务端API");
56-
return buildServerApiUrlForGitHubResource(url);
48+
logger.warn("没有可用代理,直接使用原始URL");
49+
return url;
5750
}
5851

5952
const proxiedUrl = ProxyUrlTransformer.applyProxyToUrl(url, bestProxy);
@@ -89,13 +82,9 @@ export function getProxiedUrlSync(url: string): string {
8982
return url;
9083
}
9184

92-
if (getForceServerProxy()) {
93-
return buildServerApiUrlForGitHubResource(url);
94-
}
95-
9685
const bestProxy = proxyHealthManager.getBestProxy();
9786
if (bestProxy === "") {
98-
return buildServerApiUrlForGitHubResource(url);
87+
return url;
9988
}
10089

10190
return ProxyUrlTransformer.applyProxyToUrl(url, bestProxy);

src/services/github/proxy/ProxyUrlTransformer.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { logger } from "@/utils";
22
import { getGithubConfig, getRuntimeConfig } from "@/config";
33
import { getCurrentBranch } from "../core/Config";
4-
import { getForceServerProxy } from "../config";
5-
import { buildGitHubAssetApiUrl } from "../core/content/serverApiUrls";
64

75
const githubConfig = getGithubConfig();
86
const runtimeConfig = getRuntimeConfig();
@@ -117,22 +115,6 @@ function transformImageUrl(
117115
try {
118116
const normalizedSrc = src.replace(/\\/g, "/");
119117

120-
if (getForceServerProxy() && normalizedSrc.startsWith("http")) {
121-
try {
122-
const host = new URL(normalizedSrc).hostname;
123-
if (!isGithubHost(host)) {
124-
logger.debug("强制模式下的非GitHub域名,直接返回原URL:", normalizedSrc);
125-
return normalizedSrc;
126-
}
127-
} catch {
128-
logger.warn("强制模式解析URL失败,按GitHub域名处理");
129-
}
130-
131-
const proxyUrl = buildGitHubAssetApiUrl(normalizedSrc);
132-
logger.debug("使用服务端API代理:", proxyUrl);
133-
return proxyUrl;
134-
}
135-
136118
if (normalizedSrc.startsWith("http")) {
137119
try {
138120
const host = new URL(normalizedSrc).hostname;

src/utils/pdf/pdfPreviewHelper.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ function createFallbackLink(downloadUrl: string): void {
310310
* ```
311311
*/
312312
export async function openPDFPreview(options: PDFPreviewOptions): Promise<void> {
313-
const { fileName, downloadUrl, serverProxyUrl, theme, translations, isDev = false } = options;
313+
const { fileName, downloadUrl, serverProxyUrl, theme, translations } = options;
314314

315315
// 初始化预览窗口
316316
const newTab = initializePDFWindow(fileName, theme, translations);
@@ -323,12 +323,8 @@ export async function openPDFPreview(options: PDFPreviewOptions): Promise<void>
323323

324324
const themeColors = extractPDFThemeColors(theme);
325325

326-
// 根据环境选择下载 URL
327-
const finalDownloadUrl =
328-
serverProxyUrl ??
329-
(isDev
330-
? downloadUrl
331-
: `/api/github?action=getGitHubAsset&url=${encodeURIComponent(downloadUrl)}`);
326+
// 优先使用显式代理URL,否则直接使用传入下载地址。
327+
const finalDownloadUrl = serverProxyUrl ?? downloadUrl;
332328

333329
// 下载并显示 PDF
334330
await downloadAndDisplayPDF(newTab, finalDownloadUrl, fileName, themeColors, translations);

0 commit comments

Comments
 (0)