sunshibo 2 週間 前
コミット
a2d14dc9ef

+ 14 - 0
src/api/modules/businessInfo.ts

@@ -18,6 +18,20 @@ export const getOcrRequestByBase64 = (params: any) => {
   });
   });
 };
 };
 
 
+/** 审核通过后按图片 URL 做 OCR(imageUrls + ocrType) */
+export const getOcrRequestUrl = (params: {
+  imageUrls: string;
+  ocrType: string;
+  storeId?: string | number;
+  storeUserId?: string | number;
+}) => {
+  return httpLogin.post(`alienStore/ali/ocrRequestUrl`, params, {
+    hideBusinessErrorMessage: true,
+    loading: false,
+    encrypt: false
+  });
+};
+
 /** POST,storeId 走 URL 查询参数,请求体为 JSON */
 /** POST,storeId 走 URL 查询参数,请求体为 JSON */
 export const applyment = (data: Record<string, unknown> | object, storeId: string | number) => {
 export const applyment = (data: Record<string, unknown> | object, storeId: string | number) => {
   return httpLogin.post(`alienStore/payment/wechatPartner/v3/applyment4sub/applyment`, data, {
   return httpLogin.post(`alienStore/payment/wechatPartner/v3/applyment4sub/applyment`, data, {

+ 70 - 11
src/api/upload.js

@@ -763,8 +763,8 @@ function resolveFileContentType(file) {
  * POST /upload/oss/finalize(与 fetchOssStsToken 同基址、同鉴权方式)
  * POST /upload/oss/finalize(与 fetchOssStsToken 同基址、同鉴权方式)
  * @param {File} file
  * @param {File} file
  * @param {string} objectKey
  * @param {string} objectKey
- * @param {{ signal?: AbortSignal }} [fetchOptions]
- * @returns {Promise<{ downloadUrl?: string }>}
+ * @param {{ signal?: AbortSignal; ocrType?: string }} [fetchOptions]
+ * @returns {Promise<{ downloadUrl?: string; parsed?: unknown }>}
  */
  */
 async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
 async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
   const base = String(BASE_AI_URL || "").replace(/\/$/, "");
   const base = String(BASE_AI_URL || "").replace(/\/$/, "");
@@ -781,18 +781,24 @@ async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
     headers.Authorization = token;
     headers.Authorization = token;
   }
   }
   const url = `${base}${OSS_FINALIZE_PATH}`;
   const url = `${base}${OSS_FINALIZE_PATH}`;
+  const ocrType = String(fetchOptions.ocrType ?? "").trim();
+  const body = {
+    object_key: String(objectKey ?? "").trim(),
+    filename: String(file?.name ?? "file").trim() || "file",
+    content_type: resolveFileContentType(file),
+    size: Number(file?.size) || 0
+  };
+  if (ocrType) {
+    body.ocr_type = ocrType;
+    body.ocrType = ocrType;
+  }
   let res;
   let res;
   try {
   try {
     res = await fetch(url, {
     res = await fetch(url, {
       method: "POST",
       method: "POST",
       headers,
       headers,
       credentials: "omit",
       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
-      }),
+      body: JSON.stringify(body),
       signal: createUploadFetchSignal(fetchOptions.signal, resolveOssFinalizeFetchTimeoutMs(file))
       signal: createUploadFetchSignal(fetchOptions.signal, resolveOssFinalizeFetchTimeoutMs(file))
     });
     });
   } catch (err) {
   } catch (err) {
@@ -843,7 +849,56 @@ async function requestOssFinalizeAudit(file, objectKey, fetchOptions = {}) {
   }
   }
 
 
   const downloadUrl = (preferDownloadUrlFromBody(parsed) || "").trim();
   const downloadUrl = (preferDownloadUrlFromBody(parsed) || "").trim();
-  return downloadUrl ? { downloadUrl } : {};
+  return { ...(downloadUrl ? { downloadUrl } : {}), parsed };
+}
+
+/**
+ * 商家进件证照:STS 直传 OSS 后 POST /upload/oss/finalize(内容审核 + OCR)
+ * @param {File} file
+ * @param {string} ocrType 如 BUSINESS_LICENSE、ID_CARD
+ * @param {{ signal?: AbortSignal }} [fetchOptions]
+ * @returns {Promise<{ fileUrl: string; downloadUrl: string; parsed: unknown }>}
+ */
+export async function stsUploadAndFinalizeOcr(file, ocrType, fetchOptions = {}) {
+  const uploaded = await putFileToOssWithSts(file, fetchOptions);
+  const audit = await requestOssFinalizeAudit(file, uploaded.objectKey, {
+    ...fetchOptions,
+    ocrType: String(ocrType ?? "").trim()
+  });
+  const downloadUrl = (audit.downloadUrl || uploaded.downloadUrl || uploaded.fileUrl || "").trim();
+  const fileUrl = downloadUrl || uploaded.fileUrl;
+  if (!fileUrl) {
+    throw new Error("上传失败,未返回地址");
+  }
+  return { fileUrl, downloadUrl: downloadUrl || fileUrl, parsed: audit.parsed ?? null };
+}
+
+/**
+ * STS 直传 OSS(不 finalize),可选上传弹层
+ * @param {File} file
+ * @param {{ skipSimpleUploadOverlay?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
+ * @returns {Promise<{ fileUrl: string; downloadUrl: string; objectKey: string }>}
+ */
+export async function uploadFileToOssStsOnlyWithMeta(file, options = {}) {
+  return uploadSingleFileWithOssOverlay(file, {
+    skipFinalize: true,
+    uploadSuccessMessage: options.uploadSuccessMessage ?? null,
+    ...options
+  });
+}
+
+/**
+ * OSS 直传完成后 POST finalize(内容审核 + 可选 OCR)
+ * @param {File} file
+ * @param {string} objectKey
+ * @param {string} [ocrType]
+ * @param {{ signal?: AbortSignal }} [fetchOptions]
+ */
+export async function requestOssFinalizeWithOcr(file, objectKey, ocrType, fetchOptions = {}) {
+  return requestOssFinalizeAudit(file, objectKey, {
+    ...fetchOptions,
+    ocrType: String(ocrType ?? "").trim()
+  });
 }
 }
 
 
 /**
 /**
@@ -862,14 +917,18 @@ async function putFileToOssWithStsAndFinalize(file, fetchOptions = {}) {
 /** 仅 STS 直传 OSS,不走 /upload/oss/finalize(证照等由页面单独 OCR 审核) */
 /** 仅 STS 直传 OSS,不走 /upload/oss/finalize(证照等由页面单独 OCR 审核) */
 async function putFileToOssWithStsOnly(file, fetchOptions = {}) {
 async function putFileToOssWithStsOnly(file, fetchOptions = {}) {
   const uploaded = await putFileToOssWithSts(file, fetchOptions);
   const uploaded = await putFileToOssWithSts(file, fetchOptions);
-  return { fileUrl: uploaded.fileUrl, downloadUrl: uploaded.downloadUrl };
+  return {
+    fileUrl: uploaded.fileUrl,
+    downloadUrl: uploaded.downloadUrl,
+    objectKey: uploaded.objectKey
+  };
 }
 }
 
 
 /**
 /**
  * 单文件 OSS + 审核,带可选全局上传弹层
  * 单文件 OSS + 审核,带可选全局上传弹层
  * @param {File} file
  * @param {File} file
  * @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; skipFinalize?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
  * @param {{ showLoading?: boolean; skipSimpleUploadOverlay?: boolean; skipFinalize?: boolean; uploadSuccessMessage?: string | null; uploadOverlayTitle?: string }} [options]
- * @returns {Promise<{ fileUrl: string; downloadUrl: string }>}
+ * @returns {Promise<{ fileUrl: string; downloadUrl: string; objectKey?: string }>}
  */
  */
 async function uploadSingleFileWithOssOverlay(file, options = {}) {
 async function uploadSingleFileWithOssOverlay(file, options = {}) {
   const {
   const {

ファイルの差分が大きいため隠しています
+ 0 - 0
src/utils/businessInfoImageUpload.ts


+ 51 - 25
src/views/businessInfo/subjectInfo.vue

@@ -221,14 +221,38 @@ import { ElMessage } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import type { FormInstance, FormRules } from "element-plus";
 import type { UploadRequestOptions, UploadUserFile } from "element-plus";
 import type { UploadRequestOptions, UploadUserFile } from "element-plus";
 import { Plus, InfoFilled } from "@element-plus/icons-vue";
 import { Plus, InfoFilled } from "@element-plus/icons-vue";
-import { getOcrRequestByBase64 } from "@/api/modules/businessInfo";
+import { getOcrRequestUrl } from "@/api/modules/businessInfo";
 import {
 import {
-  uploadBusinessInfoImageWithPageOcr,
+  uploadBusinessInfoImageWithFinalizeOcr,
   filterOutUploadUserFileByUid,
   filterOutUploadUserFileByUid,
   failBusinessInfoUploadCleanup
   failBusinessInfoUploadCleanup
 } from "@/utils/businessInfoImageUpload";
 } from "@/utils/businessInfoImageUpload";
 import { localGet, localSet } from "@/utils/index";
 import { localGet, localSet } from "@/utils/index";
 
 
+const GEEKER_USER_KEY = "geeker-user";
+
+function buildOcrRequestUrlParams(imageUrls: string, ocrType: string) {
+  const params: {
+    imageUrls: string;
+    ocrType: string;
+    storeId?: string | number;
+    storeUserId?: string | number;
+  } = { imageUrls, ocrType };
+  const geeker = localGet(GEEKER_USER_KEY) as
+    | { userInfo?: { storeId?: string | number | null; id?: string | number | null } }
+    | null
+    | undefined;
+  const storeId = geeker?.userInfo?.storeId ?? localGet("createdId");
+  const storeUserId = geeker?.userInfo?.id;
+  if (storeId !== undefined && storeId !== null && String(storeId).trim() !== "") {
+    params.storeId = storeId;
+  }
+  if (storeUserId !== undefined && storeUserId !== null && String(storeUserId).trim() !== "") {
+    params.storeUserId = storeUserId;
+  }
+  return params;
+}
+
 const BUSINESS_DATA_CACHE_KEY = "businessData";
 const BUSINESS_DATA_CACHE_KEY = "businessData";
 
 
 const formRef = ref<FormInstance>();
 const formRef = ref<FormInstance>();
@@ -252,7 +276,7 @@ function clearBusinessLicenseOcr() {
   businessLicenseOcr.legalPerson = "";
   businessLicenseOcr.legalPerson = "";
 }
 }
 
 
-/** 从 ocrRequestByBase64 成功响应中取出 creditCode / companyName / legalPerson */
+/** 从 ocrRequestUrl 响应中取出 creditCode / companyName / legalPerson */
 function pickBusinessLicenseOcrFields(res: any) {
 function pickBusinessLicenseOcrFields(res: any) {
   const raw = res?.data ?? res;
   const raw = res?.data ?? res;
   let node: any = raw;
   let node: any = raw;
@@ -291,7 +315,7 @@ function splitIdCardValidPeriod(raw: string): { begin: string; end: string } {
   return { begin: "", end: "" };
   return { begin: "", end: "" };
 }
 }
 
 
-/** 人像面 getOcrRequestByBase64(ID_CARD) 成功响应 */
+/** 人像面 ocrRequestUrl(ID_CARD) 成功响应 */
 function pickIdCardPortraitOcrFields(res: any): {
 function pickIdCardPortraitOcrFields(res: any): {
   name: string;
   name: string;
   idNumber: string;
   idNumber: string;
@@ -659,16 +683,17 @@ function mergeIdCardOcrFromFields(fields: ReturnType<typeof pickIdCardPortraitOc
   }
   }
 }
 }
 
 
-async function requestIdCardPortraitOcr(file: File) {
-  const formData = new FormData();
-  formData.append("imageFile", file, file.name || "id-portrait.jpg");
-  formData.append("ocrType", "ID_CARD");
+async function requestIdCardPortraitOcrByUrl(imageUrl: string) {
   isIdPortraitOcrProcessing.value = true;
   isIdPortraitOcrProcessing.value = true;
   try {
   try {
-    const res: any = await getOcrRequestByBase64(formData);
+    const res: any = await getOcrRequestUrl(buildOcrRequestUrlParams(imageUrl, "ID_CARD"));
     if (res?.code === 200 || res?.code === "200") {
     if (res?.code === 200 || res?.code === "200") {
       const fields = pickIdCardPortraitOcrFields(res);
       const fields = pickIdCardPortraitOcrFields(res);
       mergeIdCardOcrFromFields(fields, "portrait");
       mergeIdCardOcrFromFields(fields, "portrait");
+      if (!fields.name && !fields.idNumber) {
+        clearIdPortraitOcr();
+        throw new Error("身份证人像面识别未通过,请核对照片清晰度");
+      }
       return;
       return;
     }
     }
     clearIdPortraitOcr();
     clearIdPortraitOcr();
@@ -678,14 +703,11 @@ async function requestIdCardPortraitOcr(file: File) {
   }
   }
 }
 }
 
 
-/** 国徽面单独 OCR,合并有效期限(及背面地址等),不清空人像面已识别字段 */
-async function requestIdCardEmblemOcr(file: File) {
-  const formData = new FormData();
-  formData.append("imageFile", file, file.name || "id-emblem.jpg");
-  formData.append("ocrType", "ID_CARD");
+/** 国徽面 ocrRequestUrl,合并有效期限(及背面地址等),不清空人像面已识别字段 */
+async function requestIdCardEmblemOcrByUrl(imageUrl: string) {
   isIdPortraitOcrProcessing.value = true;
   isIdPortraitOcrProcessing.value = true;
   try {
   try {
-    const res: any = await getOcrRequestByBase64(formData);
+    const res: any = await getOcrRequestUrl(buildOcrRequestUrlParams(imageUrl, "ID_CARD"));
     if (res?.code === 200 || res?.code === "200") {
     if (res?.code === 200 || res?.code === "200") {
       const fields = pickIdCardPortraitOcrFields(res);
       const fields = pickIdCardPortraitOcrFields(res);
       mergeIdCardOcrFromFields(fields, "emblem");
       mergeIdCardOcrFromFields(fields, "emblem");
@@ -700,16 +722,17 @@ async function requestIdCardEmblemOcr(file: File) {
   }
   }
 }
 }
 
 
-async function requestBusinessLicenseOcr(file: File) {
-  const formData = new FormData();
-  formData.append("imageFile", file, file.name || "license.jpg");
-  formData.append("ocrType", "BUSINESS_LICENSE");
-  const res: any = await getOcrRequestByBase64(formData);
+async function requestBusinessLicenseOcrByUrl(imageUrl: string) {
+  const res: any = await getOcrRequestUrl(buildOcrRequestUrlParams(imageUrl, "BUSINESS_LICENSE"));
   if (res?.code === 200 || res?.code === "200") {
   if (res?.code === 200 || res?.code === "200") {
     const fields = pickBusinessLicenseOcrFields(res);
     const fields = pickBusinessLicenseOcrFields(res);
     businessLicenseOcr.creditCode = fields.creditCode;
     businessLicenseOcr.creditCode = fields.creditCode;
     businessLicenseOcr.companyName = fields.companyName;
     businessLicenseOcr.companyName = fields.companyName;
     businessLicenseOcr.legalPerson = fields.legalPerson;
     businessLicenseOcr.legalPerson = fields.legalPerson;
+    if (!fields.creditCode && !fields.companyName && !fields.legalPerson) {
+      clearBusinessLicenseOcr();
+      throw new Error("营业执照识别未通过,请核对照片清晰度");
+    }
     return;
     return;
   }
   }
   clearBusinessLicenseOcr();
   clearBusinessLicenseOcr();
@@ -729,9 +752,12 @@ async function handleLicenseUpload(options: UploadRequestOptions) {
 
 
   uploadFileItem.status = "uploading";
   uploadFileItem.status = "uploading";
   try {
   try {
-    const { fileUrl, mediaId } = await uploadBusinessInfoImageWithPageOcr(file, requestBusinessLicenseOcr, {
-      showUploadOverlay: true
-    });
+    const { fileUrl, mediaId } = await uploadBusinessInfoImageWithFinalizeOcr(
+      file,
+      "BUSINESS_LICENSE",
+      requestBusinessLicenseOcrByUrl,
+      { showUploadOverlay: true }
+    );
     uploadFileItem.status = "success";
     uploadFileItem.status = "success";
     uploadFileItem.url = fileUrl;
     uploadFileItem.url = fileUrl;
     uploadFileItem.response = { media_id: mediaId, url: fileUrl };
     uploadFileItem.response = { media_id: mediaId, url: fileUrl };
@@ -786,8 +812,8 @@ async function handleIdCardUpload(options: UploadRequestOptions, side: IdCardSid
 
 
   uploadFileItem.status = "uploading";
   uploadFileItem.status = "uploading";
   try {
   try {
-    const runOcr = side === "portrait" ? requestIdCardPortraitOcr : requestIdCardEmblemOcr;
-    const { fileUrl, mediaId } = await uploadBusinessInfoImageWithPageOcr(file, runOcr, {
+    const runOcrByUrl = side === "portrait" ? requestIdCardPortraitOcrByUrl : requestIdCardEmblemOcrByUrl;
+    const { fileUrl, mediaId } = await uploadBusinessInfoImageWithFinalizeOcr(file, "ID_CARD", runOcrByUrl, {
       showUploadOverlay: true
       showUploadOverlay: true
     });
     });
     if (side === "portrait") {
     if (side === "portrait") {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません