Skip to content

Commit f6ad6bc

Browse files
committed
per: 统一前端日志架构
fix: 禁用多文件上传时的自定义链接后缀
1 parent a09b10d commit f6ad6bc

File tree

167 files changed

+1889
-1580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+1889
-1580
lines changed

frontend/src/App.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import AnnouncementModal from "@/modules/admin/components/AnnouncementModal.vue"
1515
import { IconBell, IconClose, IconComputerDesktop, IconGithub, IconHamburger, IconMoon, IconSun } from "@/components/icons";
1616
import { Notivue, NotivueSwipe, Notification } from "notivue";
1717
import { cloudPasteLightTheme, cloudPasteDarkTheme } from "@/styles/notivueTheme";
18+
import { createLogger } from "@/utils/logger.js";
1819
1920
const route = useRoute();
21+
const log = createLogger("App");
2022
2123
// 使用认证Store和站点配置Store
2224
const authStore = useAuthStore();
@@ -127,7 +129,7 @@ const isDev = import.meta.env.DEV;
127129
showEnvSwitcher.value = hasAdminToken && hasEnvParam;
128130
}
129131
130-
console.log("应用初始化完成");
132+
log.debug("应用初始化完成");
131133
132134
useEventListener(window, "global-message", handleGlobalMessageEvent);
133135
useEventListener(window, "global-message-clear", handleGlobalMessageClearEvent);

frontend/src/api/client.js

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import { getFullApiUrl } from "./config";
77
import { ApiStatus } from "./ApiStatus"; // 导入API状态码常量
88
import { logoutViaBridge, buildAuthHeaders } from "@/modules/security/index.js";
99
import { enqueueOfflineOperation } from "@/modules/pwa-offline/index.js";
10+
import { createLogger } from "@/utils/logger.js";
1011
import { useOnline } from "@vueuse/core";
1112

1213
const isOnline = useOnline();
1314

15+
const apiLog = createLogger("API");
16+
1417
// - 优先使用后端返回的 message
1518
// - 附带请求ID(X-Request-Id)方便排查
1619
function extractRequestIdFromResponse(response) {
@@ -98,11 +101,11 @@ async function addAuthToken(headers) {
98101
const merged = buildAuthHeaders(headers);
99102

100103
if (headers.Authorization) {
101-
console.log("使用传入的Authorization头:", headers.Authorization);
104+
apiLog.debug("请求已携带 Authorization(来自调用方)");
102105
} else if (merged.Authorization) {
103-
console.log("ͨ通过authBridge添加Authorization头");
106+
apiLog.debug("请求已携带 Authorization(来自 authBridge)");
104107
} else {
105-
console.log("未找到认证凭据,请求将不包含Authorization头");
108+
apiLog.debug("请求未携带 Authorization");
106109
}
107110

108111
return merged;
@@ -157,11 +160,14 @@ export async function fetchApi(endpoint, options = {}) {
157160
timestamp: new Date().toISOString(),
158161
};
159162

160-
console.log(`🚀 API请求: ${debugInfo.method} ${debugInfo.url}`, debugInfo);
163+
apiLog.debug(`API请求: ${debugInfo.method} ${debugInfo.url}`, {
164+
hasBody: !!debugInfo.body,
165+
headerKeys: Object.keys(debugInfo.headers || {}),
166+
});
161167

162-
// 🎯 PWA网络状态检测 - 符合最佳实践
168+
// PWA网络状态检测 - 符合最佳实践
163169
if (!isOnline.value) {
164-
console.warn(`🔌 离线状态,API请求可能失败: ${url}`);
170+
apiLog.warn("离线状态,API请求可能失败:", url);
165171
if (options.method && options.method !== "GET") {
166172
await enqueueOfflineOperation(endpoint, options);
167173
}
@@ -220,21 +226,18 @@ export async function fetchApi(endpoint, options = {}) {
220226
const endTime = Date.now();
221227
const timeTaken = endTime - startTime;
222228

223-
console.log(`⏱️ API响应耗时: ${timeTaken}ms, 状态: ${response.status}`, {
229+
apiLog.debug(`API响应耗时: ${timeTaken}ms, 状态: ${response.status}`, {
224230
url,
225231
status: response.status,
226232
statusText: response.statusText,
227-
headers: Object.fromEntries([...response.headers.entries()]),
228233
});
229234

230235
// 304 Not Modified:成熟项目常用的条件请求语义(If-None-Match)
231236
// - 304 无响应体,不应尝试解析 JSON
232237
// - 交由上层用本地缓存数据兜底
233238
if (response.status === 304) {
234239
const etag = response.headers.get("etag") || response.headers.get("ETag") || null;
235-
if (import.meta?.env?.DEV) {
236-
console.log(`📦 API响应(${url}): 304 Not Modified`, { url, etag });
237-
}
240+
apiLog.debug(`API响应: 304 Not Modified`, { url, etag });
238241
return {
239242
success: true,
240243
notModified: true,
@@ -252,13 +255,18 @@ export async function fetchApi(endpoint, options = {}) {
252255
// 检查是否需要返回blob响应
253256
if (options.responseType === "blob") {
254257
responseData = await response.blob();
255-
console.log(`📦 API响应Blob(${url}): ${responseData.size} 字节, 类型: ${responseData.type}`);
258+
apiLog.debug(`API响应Blob: ${responseData.size} 字节`, { url, type: responseData.type });
256259
} else if (contentType && contentType.includes("application/json")) {
257260
responseData = await response.json();
258-
console.log(`📦 API响应数据(${url}):`, responseData);
261+
apiLog.debug(`API响应JSON`, {
262+
url,
263+
kind: Array.isArray(responseData) ? "array" : typeof responseData,
264+
keys: responseData && typeof responseData === "object" && !Array.isArray(responseData) ? Object.keys(responseData).slice(0, 20) : undefined,
265+
length: Array.isArray(responseData) ? responseData.length : undefined,
266+
});
259267
} else {
260268
responseData = await response.text();
261-
console.log(`📝 API响应文本(${url}): ${responseData.substring(0, 100)}${responseData.length > 100 ? "..." : ""}`);
269+
apiLog.debug(`API响应文本: ${responseData.substring(0, 100)}${responseData.length > 100 ? "..." : ""}`);
262270
}
263271

264272
// 如果响应不成功,抛出错误
@@ -277,15 +285,15 @@ export async function fetchApi(endpoint, options = {}) {
277285

278286
// 特殊处理401未授权错误
279287
if (response.status === ApiStatus.UNAUTHORIZED) {
280-
console.error(`🚫 授权失败(${url}):`, responseData);
288+
apiLog.error(`🚫 授权失败(${url}):`, responseData);
281289

282290
// 检查特殊的密码验证请求类型
283291
const isPasswordRelatedRequest = checkPasswordRelatedRequest(endpoint, options);
284292
const { isPasswordVerify, isChangePasswordRequest } = isPasswordRelatedRequest;
285293

286294
// 如果是密码验证请求,直接返回错误,不清除令牌
287295
if (isPasswordVerify) {
288-
console.log(`密码验证失败不清除认证令牌端点: ${endpoint}`);
296+
apiLog.debug(`密码验证失败不清除认证令牌端点: ${endpoint}`);
289297

290298
// 确保返回后端提供的具体错误信息
291299
const errorMessage = responseData && responseData.message ? responseData.message : "密码错误";
@@ -327,7 +335,7 @@ export async function fetchApi(endpoint, options = {}) {
327335
const isAdminAuthEndpoint = endpoint.startsWith("/admin") || endpoint.includes("/admin/");
328336

329337
if (isAuthErrorCode || isAdminAuthEndpoint) {
330-
console.log("管理员令牌验证失败执行登出");
338+
apiLog.debug("管理员令牌验证失败执行登出");
331339
await logoutViaBridge();
332340
const error = new Error("管理员会话已过期,请重新登录");
333341
error.__logged = true;
@@ -357,13 +365,13 @@ export async function fetchApi(endpoint, options = {}) {
357365
responseData.message.includes("没有权限"));
358366

359367
if (isPermissionIssue) {
360-
console.log("API密钥权限不足不执行登出");
368+
apiLog.debug("API密钥权限不足不执行登出");
361369
const error = new Error(responseData.message || "访问被拒绝,您可能无权执行此操作");
362370
error.__logged = true;
363371
throw error;
364372
}
365373

366-
console.log("API密钥验证失败执行登出");
374+
apiLog.debug("API密钥验证失败执行登出");
367375
await logoutViaBridge();
368376
const apiKeyError = new Error("API密钥无效或已过期");
369377
apiKeyError.__logged = true;
@@ -377,7 +385,7 @@ export async function fetchApi(endpoint, options = {}) {
377385

378386
// 对409状态码做特殊处理(链接后缀冲突或其他冲突)
379387
if (response.status === ApiStatus.CONFLICT) {
380-
console.error(`❌ 资源冲突错误(${url}):`, responseData);
388+
apiLog.error(`❌ 资源冲突错误(${url}):`, responseData);
381389
// 使用后端返回的具体错误信息,无论是字符串形式还是对象形式
382390
if (typeof responseData === "string") {
383391
const error = new Error(responseData);
@@ -396,7 +404,7 @@ export async function fetchApi(endpoint, options = {}) {
396404

397405
// 处理新的后端错误格式 (code, message)
398406
if (responseData && typeof responseData === "object") {
399-
console.error(`❌ API错误(${url}):`, responseData);
407+
apiLog.error(`❌ API错误(${url}):`, responseData);
400408
const baseMessage = responseData.message || `HTTP错误 ${response.status}: ${response.statusText}`;
401409
const payloadRequestId =
402410
typeof responseData.requestId === "string" && responseData.requestId.trim()
@@ -420,7 +428,7 @@ export async function fetchApi(endpoint, options = {}) {
420428
throw error;
421429
}
422430

423-
console.error(`❌ HTTP错误(${url}): ${response.status}`, responseData);
431+
apiLog.error(`❌ HTTP错误(${url}): ${response.status}`, responseData);
424432
const error = new Error(appendRequestIdIfNeeded(`HTTP错误 ${response.status}: ${response.statusText}`, requestId));
425433
error.__logged = true;
426434
if (requestId) {
@@ -434,7 +442,7 @@ export async function fetchApi(endpoint, options = {}) {
434442
// success 布尔判断
435443
if ("success" in responseData) {
436444
if (responseData.success !== true) {
437-
console.error(`❌ API业务错误(${url}):`, responseData);
445+
apiLog.error(`❌ API业务错误(${url}):`, responseData);
438446
const baseMessage = responseData.message || "请求失败";
439447
const payloadRequestId =
440448
typeof responseData.requestId === "string" && responseData.requestId.trim()
@@ -494,23 +502,23 @@ export async function fetchApi(endpoint, options = {}) {
494502
// 处理不同类型的错误
495503
if (error.name === "AbortError") {
496504
// 请求被主动取消时,静默处理,不抛出错误
497-
console.log(`⏹️ API请求被取消(${url})`);
505+
apiLog.debug(`API请求被取消(${url})`);
498506
// 创建一个特殊的 AbortError 对象,让调用方可以识别
499507
const abortError = new Error("请求已取消");
500508
abortError.name = "AbortError";
501509
abortError.__aborted = true;
502510
abortError.__logged = true;
503511
throw abortError;
504512
} else if (error.name === "TimeoutError") {
505-
console.error(`⏰ API请求超时(${url}):`, error.message);
513+
apiLog.error(`⏰ API请求超时(${url}):`, error.message);
506514
throw new Error("请求超时,服务器响应时间过长");
507515
} else if (error.name === "TypeError" && error.message.includes("fetch")) {
508-
console.error(`🌐 网络错误(${url}):`, error.message);
516+
apiLog.error(`🌐 网络错误(${url}):`, error.message);
509517
throw new Error("网络连接失败,请检查网络设置");
510518
} else {
511519
// 避免对已经在上层记录过的业务错误重复打印日志
512520
if (!error.__logged) {
513-
console.error(`❌ API请求失败(${url}):`, error);
521+
apiLog.error(`❌ API请求失败(${url}):`, error);
514522
}
515523
// 兜底:保证抛出去的一定是 Error,避免上层拿不到 error.message 而只能显示“未知错误”
516524
if (error instanceof Error) {
@@ -537,19 +545,19 @@ async function handleSuccessfulResponse(endpoint, options, responseData) {
537545
if (method === "POST" && endpoint.includes("/paste") && responseData.data) {
538546
// 存储新创建的文本分享
539547
await pwaUtils.storage.savePaste(responseData.data);
540-
console.log(`[PWA] 已存储文本分享: ${responseData.data.slug}`);
548+
apiLog.debug(`[PWA] 已存储文本分享: ${responseData.data.slug}`);
541549
} else if (method === "POST" && endpoint.includes("/upload") && responseData.data) {
542550
// 存储上传的文件信息
543551
await pwaUtils.storage.saveFile(responseData.data);
544-
console.log(`[PWA] 已存储文件信息: ${responseData.data.filename || responseData.data.slug}`);
552+
apiLog.debug(`[PWA] 已存储文件信息: ${responseData.data.filename || responseData.data.slug}`);
545553
} else if (method === "POST" && endpoint.includes("/admin/settings")) {
546554
// 存储重要设置更新
547555
const settingKey = `admin_setting_${Date.now()}`;
548556
await pwaUtils.storage.saveSetting(settingKey, responseData);
549-
console.log(`[PWA] 已存储管理员设置: ${settingKey}`);
557+
apiLog.debug(`[PWA] 已存储管理员设置: ${settingKey}`);
550558
}
551559
} catch (error) {
552-
console.warn("[PWA] 业务数据存储失败:", error);
560+
apiLog.warn("[PWA] 业务数据存储失败:", error);
553561
}
554562
}
555563

@@ -591,7 +599,7 @@ export async function post(endpoint, data, options = {}) {
591599
partInfo = `,分片: ${partNumber}${isLastPart ? " (最后分片)" : ""}`;
592600
}
593601

594-
console.log(`发送二进制数据到 ${url}${partInfo},大小: ${data instanceof Blob ? data.size : data.byteLength} 字节`);
602+
apiLog.debug(`发送二进制数据到 ${url}${partInfo}`, { size: data instanceof Blob ? data.size : data.byteLength });
595603

596604
// 添加对 XHR 对象的处理,以支持取消功能
597605
const xhr = new XMLHttpRequest();
@@ -647,10 +655,10 @@ export async function post(endpoint, data, options = {}) {
647655
responseData = xhr.response;
648656
}
649657

650-
console.log(`✅ 二进制上传请求成功 ${url}${partInfo}`);
658+
apiLog.debug(`二进制上传请求成功 ${url}${partInfo}`);
651659
resolve(responseData);
652660
} catch (e) {
653-
console.error(`解析响应错误: ${e.message}`);
661+
apiLog.error(`解析响应错误: ${e.message}`);
654662
reject(new Error(`解析响应错误: ${e.message}`));
655663
}
656664
} else {
@@ -672,14 +680,14 @@ export async function post(endpoint, data, options = {}) {
672680
errorMsg = `HTTP错误 ${xhr.status}`;
673681
}
674682

675-
console.error(`❌ 二进制上传请求失败 ${url}${partInfo}: ${errorMsg}`);
683+
apiLog.error(`❌ 二进制上传请求失败 ${url}${partInfo}: ${errorMsg}`);
676684
reject(new Error(errorMsg));
677685
}
678686
};
679687

680688
// 监听网络错误
681689
xhr.onerror = function () {
682-
console.error(`❌ 网络错误: ${url}${partInfo}`);
690+
apiLog.error(`❌ 网络错误: ${url}${partInfo}`);
683691
reject(new Error("网络错误,请检查连接"));
684692
};
685693

@@ -688,13 +696,13 @@ export async function post(endpoint, data, options = {}) {
688696

689697
// 监听超时
690698
xhr.ontimeout = function () {
691-
console.error(`❌ 请求超时: ${url}${partInfo}`);
699+
apiLog.error(`❌ 请求超时: ${url}${partInfo}`);
692700
reject(new Error("请求超时,服务器响应时间过长"));
693701
};
694702

695703
// 监听中止
696704
xhr.onabort = function () {
697-
console.log(`⏹️ 请求已被中止: ${url}${partInfo}`);
705+
apiLog.debug(`请求已被中止: ${url}${partInfo}`);
698706
reject(new Error("请求已被用户取消"));
699707
};
700708

@@ -710,7 +718,7 @@ export async function post(endpoint, data, options = {}) {
710718
body: data,
711719
});
712720
} catch (error) {
713-
console.error(`POST ${endpoint} 请求错误:`, error);
721+
apiLog.error(`POST ${endpoint} 请求错误:`, error);
714722
throw error;
715723
}
716724
}

frontend/src/api/config.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66

77
import { useLocalStorage } from "@vueuse/core";
8+
import { createLogger } from "@/utils/logger.js";
89

910
// 默认的开发环境API基础URL
1011
const DEFAULT_DEV_API_URL = "http://localhost:8787";
12+
const log = createLogger("ApiConfig");
1113

1214
// 检查是否在Docker环境中运行
1315
const isDockerEnvironment = () => {
@@ -21,7 +23,7 @@ function getApiBaseUrl() {
2123
const runtimeUrl = window.appConfig.backendUrl;
2224
// 统一使用__BACKEND_URL__作为占位符,避免不同环境处理逻辑不一致
2325
if (runtimeUrl !== "__" + "BACKEND_URL__") {
24-
console.log("使用运行时配置的后端URL:", runtimeUrl);
26+
log.debug("使用运行时配置的后端URL:", runtimeUrl);
2527
return runtimeUrl;
2628
}
2729
}
@@ -30,7 +32,7 @@ function getApiBaseUrl() {
3032
if (!isDockerEnvironment() && typeof window !== "undefined" && window.localStorage) {
3133
const storedUrl = useLocalStorage("vite-api-base-url", "").value;
3234
if (storedUrl) {
33-
console.log("非Docker环境:使用localStorage中的后端URL:", storedUrl);
35+
log.debug("非Docker环境:使用localStorage中的后端URL:", storedUrl);
3436
return storedUrl;
3537
}
3638
}
@@ -43,7 +45,7 @@ function getApiBaseUrl() {
4345

4446
// 生产环境:单 Worker 部署时使用同源(Cloudflare Workers SPA 模式)
4547
if (import.meta.env.PROD && typeof window !== "undefined") {
46-
console.log("生产环境:使用同源后端", window.location.origin);
48+
log.debug("生产环境:使用同源后端", window.location.origin);
4749
return window.location.origin;
4850
}
4951

frontend/src/api/services/systemService.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
*/
55

66
import { get, post, put } from "../client";
7+
import { createLogger } from "@/utils/logger.js";
8+
9+
const log = createLogger("SystemService");
710

811
/******************************************************************************
912
* 仪表盘统计API
@@ -33,7 +36,7 @@ export async function getMaxUploadSize() {
3336
}
3437
return 100; // 默认值
3538
} catch (error) {
36-
console.error("获取最大上传大小失败:", error);
39+
log.error("获取最大上传大小失败:", error);
3740
return 100; // 出错时返回默认值
3841
}
3942
}

frontend/src/api/services/urlUploadService.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import { post } from "../client";
88
import { getFullApiUrl } from "../config";
99
import { completeFileUpload } from "./fileService";
10+
import { createLogger } from "@/utils/logger.js";
11+
12+
const log = createLogger("UrlUpload");
1013

1114
/**
1215
* 验证URL并获取文件元信息
@@ -59,19 +62,19 @@ function isCorsError(error) {
5962
export async function fetchUrlContent(options) {
6063
// 首先尝试直接获取URL内容
6164
try {
62-
console.log(`尝试直接获取URL: ${options.url}`);
65+
log.debug(`尝试直接获取URL: ${options.url}`);
6366
return await fetchFromUrl(options.url, options.onProgress, options.setXhr, "directDownload");
6467
} catch (error) {
6568
// 用户主动取消下载时,直接抛出错误,不尝试代理重试
6669
if (error.message === "下载已取消") {
6770
throw error;
6871
}
6972

70-
console.warn(`直接获取URL内容失败: ${error.message}`);
73+
log.warn(`直接获取URL内容失败: ${error.message}`);
7174

7275
// 检查是否是CORS错误或其他网络错误,如果是则尝试使用代理
7376
if (isCorsError(error) || error.message.includes("获取URL内容失败")) {
74-
console.log(`检测到跨域问题,切换到代理模式获取URL: ${options.url}`);
77+
log.debug(`检测到跨域问题,切换到代理模式获取URL: ${options.url}`);
7578
// 使用代理URL重试
7679
const proxyUrl = getProxyUrl(options.url);
7780
return await fetchFromUrl(proxyUrl, options.onProgress, options.setXhr, "proxyDownload");

0 commit comments

Comments
 (0)