Преглед на файлове

fix: 上传oss后加上审核接口

sgc преди 3 седмици
родител
ревизия
1542139be1
променени са 1 файла, в които са добавени 110 реда и са изтрити 3 реда
  1. 110 3
      src/api/upload.js

+ 110 - 3
src/api/upload.js

@@ -11,6 +11,9 @@ const SIMPLE_UPLOAD_PATH = "/upload/simple";
 /** OSS STS 临时凭证(官方相册等直传 OSS) */
 const OSS_STS_TOKEN_PATH = "/upload/oss/sts-token";
 
+/** OSS 直传成功后的 AI 审核(与 sts-token 同基址 BASE_AI_URL) */
+const OSS_FINALIZE_PATH = "/upload/oss/finalize";
+
 /** 浏览器默认可达;内网可配 VITE_OSS_UPLOAD_ENDPOINT=oss-cn-beijing-internal.aliyuncs.com */
 const DEFAULT_OSS_UPLOAD_ENDPOINT = "oss-cn-beijing.aliyuncs.com";
 
@@ -563,7 +566,105 @@ async function putFileToOssWithSts(file, fetchOptions = {}) {
   });
   const fileUrl =
     (typeof result.url === "string" && result.url.trim()) || buildOssObjectPublicUrl(sts.bucket, endpoint, objectKey);
-  return { fileUrl, downloadUrl: fileUrl };
+  return { fileUrl, downloadUrl: fileUrl, objectKey };
+}
+
+/**
+ * @param {File} file
+ * @returns {string}
+ */
+function resolveFileContentType(file) {
+  const t = String(file?.type ?? "").trim();
+  if (t) return t;
+  const ext = getFileExtension(file?.name, "image");
+  const videoMap = { mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogg: "video/ogg" };
+  if (videoMap[ext]) return videoMap[ext];
+  const imageMap = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
+  return imageMap[ext] || "image/jpeg";
+}
+
+/**
+ * POST /upload/oss/finalize(与 fetchOssStsToken 同基址、同鉴权方式)
+ * @param {File} file
+ * @param {string} objectKey
+ * @param {{ signal?: AbortSignal }} [fetchOptions]
+ * @returns {Promise<{ downloadUrl?: string }>}
+ */
+async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
+  const base = String(BASE_AI_URL || "").replace(/\/$/, "");
+  if (!base) {
+    throw new Error("未配置上传服务地址(VITE_AI_UPLOAD_BASE 或默认 /ai-upload)");
+  }
+  const userStore = useUserStore();
+  const headers = {
+    Accept: "application/json",
+    "Content-Type": "application/json"
+  };
+  const token = userStore.token || "";
+  if (token) {
+    headers.Authorization = token;
+  }
+  const url = `${base}${OSS_FINALIZE_PATH}`;
+  let res;
+  try {
+    res = await fetch(url, {
+      method: "POST",
+      headers,
+      credentials: "omit",
+      body: JSON.stringify({
+        object_key: String(objectKey ?? "").trim(),
+        filename: String(file?.name ?? "file").trim() || "file",
+        content_type: resolveFileContentType(file),
+        size: Number(file?.size) || 0
+      }),
+      signal: fetchOptions.signal ?? undefined
+    });
+  } catch (err) {
+    if (isUploadUserCancelledError(err)) {
+      throw err instanceof Error ? err : new Error(String(err ?? "AbortError"));
+    }
+    console.error("[oss/finalize] 请求失败:", url, err);
+    throw new Error("内容审核请求失败");
+  }
+
+  const rawText = await res.text();
+  let parsed = null;
+  const trimmed = rawText.trim();
+  if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
+    try {
+      parsed = JSON.parse(rawText);
+    } catch (_) {
+      /* ignore */
+    }
+  }
+
+  if (!res.ok) {
+    const modHint = formatSimpleUploadModerationMessage(parsed);
+    let msg = modHint;
+    if (!msg && parsed && typeof parsed === "object") {
+      const p = /** @type {Record<string, unknown>} */ (parsed);
+      const m = p.msg ?? p.message ?? p.error;
+      if (m != null && String(m)) msg = String(m);
+    }
+    if (!msg && trimmed) msg = trimmed.slice(0, 200);
+    if (!msg) msg = "内容审核请求失败";
+    throw new Error(msg);
+  }
+
+  try {
+    assertSimpleUploadBusinessOk(parsed);
+  } catch (bizErr) {
+    console.error("[oss/finalize] 业务失败", bizErr, trimmed.slice(0, 500));
+    throw bizErr;
+  }
+
+  const moderationUserTip = formatSimpleUploadModerationMessage(parsed);
+  if (moderationUserTip) {
+    throw new Error(moderationUserTip);
+  }
+
+  const downloadUrl = (preferDownloadUrlFromBody(parsed) || "").trim();
+  return downloadUrl ? { downloadUrl } : {};
 }
 
 /**
@@ -585,7 +686,13 @@ export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
   }
 
   try {
-    const runUpload = async signal => putFileToOssWithSts(file, signal ? { signal } : {});
+    const runUpload = async signal => {
+      const fetchOpts = signal ? { signal } : {};
+      const uploaded = await putFileToOssWithSts(file, fetchOpts);
+      const audit = await requestOssFinalizeAudit(file, uploaded.objectKey, fetchOpts);
+      const downloadUrl = audit.downloadUrl?.trim() || uploaded.downloadUrl;
+      return { fileUrl: uploaded.fileUrl, downloadUrl };
+    };
 
     let result;
     if (skipSimpleUploadOverlay) {
@@ -606,7 +713,7 @@ export async function uploadFileToOssStsWithDownloadMeta(file, options = {}) {
     return result;
   } catch (e) {
     closeLoading();
-    console.error("OSS 直传失败", e);
+    console.error("OSS 直传或审核失败", e);
     if (isUploadUserCancelledError(e)) {
       throw e;
     }