feat(backend): refactor OCR client to support HTTP + subprocess dual mode

- runtime-config.js: add baseUrl field to ocr type definition
- default.cjs: add ocr.baseUrl default (empty string = subprocess mode)
- runtime.js: add OCR_BASE_URL env var parsing
- ocr.js: add HTTP mode (when OCR_BASE_URL set) alongside existing subprocess mode
  - resolveOcrMode() detects mode from runtimeConfig.ocr.baseUrl
  - callOcrViaHttp() calls FastAPI endpoints directly with payload
  - warmupOcrViaHttp() calls /health endpoint for HTTP mode warmup
  - callOcrRequest() routes between HTTP and subprocess based on mode
  - All 5 original exports preserved (recognizeTencentCaptcha, etc.)
  - Subprocess mode remains backward compatible (default when no OCR_BASE_URL)
This commit is contained in:
yml
2026-05-19 23:09:30 +08:00
parent f87238b6c0
commit bc5fd4212d
4 changed files with 437 additions and 216 deletions
+356 -214
View File
@@ -1,544 +1,686 @@
// @ts-check
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
/** @typedef {import('../types/runtime-config.js').RuntimeConfig} RuntimeConfig */
const require = createRequire(import.meta.url)
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
export const PROJECT_ROOT = path.resolve(CURRENT_DIR, '../..')
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
const CONFIG_ROOT = path.join(PROJECT_ROOT, 'config')
const require = createRequire(import.meta.url);
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
export const PROJECT_ROOT = path.resolve(CURRENT_DIR, "../..");
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, "../..");
const CONFIG_ROOT = path.join(PROJECT_ROOT, "config");
loadEnvFiles([
path.join(WORKSPACE_ROOT, '.env'),
path.join(PROJECT_ROOT, '.env'),
])
path.join(WORKSPACE_ROOT, ".env"),
path.join(PROJECT_ROOT, ".env"),
]);
const defaultConfig = /** @type {RuntimeConfig} */ (loadConfig(path.join(CONFIG_ROOT, 'default.cjs')))
const defaultConfig = /** @type {RuntimeConfig} */ (
loadConfig(path.join(CONFIG_ROOT, "default.cjs"))
);
export const runtimeConfig = /** @type {RuntimeConfig} */ (applyEnvOverrides(defaultConfig))
export const runtimeConfig = /** @type {RuntimeConfig} */ (
applyEnvOverrides(defaultConfig)
);
function loadConfig(configPath) {
if (!fs.existsSync(configPath)) {
return {}
return {};
}
const loaded = require(configPath)
return isPlainObject(loaded) ? loaded : {}
const loaded = require(configPath);
return isPlainObject(loaded) ? loaded : {};
}
function loadEnvFiles(filePaths) {
for (const filePath of filePaths) {
loadEnvFile(filePath)
loadEnvFile(filePath);
}
}
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) {
return
return;
}
const rawText = fs.readFileSync(filePath, 'utf8')
const lines = rawText.split(/\r?\n/)
const rawText = fs.readFileSync(filePath, "utf8");
const lines = rawText.split(/\r?\n/);
for (const rawLine of lines) {
const line = rawLine.trim()
const line = rawLine.trim();
if (!line || line.startsWith('#')) {
continue
if (!line || line.startsWith("#")) {
continue;
}
const separatorIndex = line.indexOf('=')
const separatorIndex = line.indexOf("=");
if (separatorIndex <= 0) {
continue
continue;
}
const key = line.slice(0, separatorIndex).trim()
const key = line.slice(0, separatorIndex).trim();
if (!key || key in process.env) {
continue
continue;
}
process.env[key] = parseEnvValue(line.slice(separatorIndex + 1))
process.env[key] = parseEnvValue(line.slice(separatorIndex + 1));
}
}
function parseEnvValue(rawValue) {
const value = String(rawValue || '').trim()
const value = String(rawValue || "").trim();
if (!value) {
return ''
return "";
}
const quote = value[0]
const quote = value[0];
if ((quote === '"' || quote === "'") && value.endsWith(quote)) {
return value.slice(1, -1)
return value.slice(1, -1);
}
return value
return value;
}
function applyEnvOverrides(baseConfig) {
const nextConfig = deepMerge(baseConfig, {})
const nextConfig = deepMerge(baseConfig, {});
const port = parseInteger(process.env.PORT)
const port = parseInteger(process.env.PORT);
if (port !== null) {
nextConfig.server.port = port
nextConfig.server.port = port;
}
const chromePath = String(process.env.CHROME_PATH || '').trim()
const chromePath = String(process.env.CHROME_PATH || "").trim();
if (chromePath) {
nextConfig.browser.chromePath = chromePath
nextConfig.browser.chromePath = chromePath;
}
const browserHeadless = parseBoolean(process.env.TENCENT_BROWSER_HEADLESS)
const browserHeadless = parseBoolean(process.env.TENCENT_BROWSER_HEADLESS);
if (browserHeadless !== null) {
nextConfig.browser.headless = browserHeadless
nextConfig.browser.headless = browserHeadless;
}
const browserDevtools = parseBoolean(process.env.TENCENT_BROWSER_DEVTOOLS)
const browserDevtools = parseBoolean(process.env.TENCENT_BROWSER_DEVTOOLS);
if (browserDevtools !== null) {
nextConfig.browser.devtools = browserDevtools
nextConfig.browser.devtools = browserDevtools;
}
const browserKeepAlive = parseBoolean(process.env.TENCENT_BROWSER_KEEP_ALIVE)
const browserKeepAlive = parseBoolean(process.env.TENCENT_BROWSER_KEEP_ALIVE);
if (browserKeepAlive !== null) {
nextConfig.browser.keepAlive = browserKeepAlive
nextConfig.browser.keepAlive = browserKeepAlive;
}
const browserPrewarm = parseBoolean(process.env.TENCENT_BROWSER_PREWARM)
const browserPrewarm = parseBoolean(process.env.TENCENT_BROWSER_PREWARM);
if (browserPrewarm !== null) {
nextConfig.browser.prewarm = browserPrewarm
nextConfig.browser.prewarm = browserPrewarm;
}
const browserSlowMoMs = parseInteger(process.env.TENCENT_BROWSER_SLOW_MO)
const browserSlowMoMs = parseInteger(process.env.TENCENT_BROWSER_SLOW_MO);
if (browserSlowMoMs !== null) {
nextConfig.browser.slowMoMs = browserSlowMoMs
nextConfig.browser.slowMoMs = browserSlowMoMs;
}
const sessionDebug = parseBoolean(process.env.TENCENT_SESSION_DEBUG)
const sessionDebug = parseBoolean(process.env.TENCENT_SESSION_DEBUG);
if (sessionDebug !== null) {
nextConfig.session.debug = sessionDebug
nextConfig.session.debug = sessionDebug;
}
const ocrProjectRoot = String(process.env.OCR_PROJECT_ROOT || '').trim()
const ocrProjectRoot = String(process.env.OCR_PROJECT_ROOT || "").trim();
if (ocrProjectRoot) {
nextConfig.ocr.projectRoot = ocrProjectRoot
nextConfig.ocr.projectRoot = ocrProjectRoot;
}
const dataRoot = String(process.env.DATA_ROOT || '').trim()
const ocrBaseUrl = String(process.env.OCR_BASE_URL || "").trim();
if (ocrBaseUrl) {
nextConfig.ocr.baseUrl = ocrBaseUrl;
}
const dataRoot = String(process.env.DATA_ROOT || "").trim();
if (dataRoot) {
nextConfig.data.root = path.resolve(dataRoot)
nextConfig.data.root = path.resolve(dataRoot);
}
const logLevel = String(process.env.LOG_LEVEL || '').trim()
const logLevel = String(process.env.LOG_LEVEL || "").trim();
if (logLevel) {
nextConfig.logging.level = logLevel
nextConfig.logging.level = logLevel;
}
const databaseUrl = String(process.env.DATABASE_URL || '').trim()
const databaseUrl = String(process.env.DATABASE_URL || "").trim();
if (databaseUrl) {
nextConfig.database.url = databaseUrl
nextConfig.database.url = databaseUrl;
}
const databaseSsl = parseBoolean(process.env.DATABASE_SSL)
const databaseSsl = parseBoolean(process.env.DATABASE_SSL);
if (databaseSsl !== null) {
nextConfig.database.ssl = databaseSsl
nextConfig.database.ssl = databaseSsl;
}
const databaseMaxConnections = parseInteger(process.env.DATABASE_MAX_CONNECTIONS)
const databaseMaxConnections = parseInteger(
process.env.DATABASE_MAX_CONNECTIONS
);
if (databaseMaxConnections !== null) {
nextConfig.database.maxConnections = databaseMaxConnections
nextConfig.database.maxConnections = databaseMaxConnections;
}
const claimBaseUrl = String(process.env.CLAIM_BASE_URL || '').trim()
const claimBaseUrl = String(process.env.CLAIM_BASE_URL || "").trim();
if (claimBaseUrl) {
nextConfig.orders.claimBaseUrl = claimBaseUrl
nextConfig.orders.claimBaseUrl = claimBaseUrl;
} else {
const appBaseUrl = String(process.env.APP_BASE_URL || '').trim()
const appBaseUrl = String(process.env.APP_BASE_URL || "").trim();
if (appBaseUrl) {
nextConfig.orders.claimBaseUrl = `${appBaseUrl.replace(/\/+$/, '')}/#/claim`
nextConfig.orders.claimBaseUrl = `${appBaseUrl.replace(
/\/+$/,
""
)}/#/claim`;
}
}
const tokenTtlHours = parseInteger(process.env.CLAIM_TOKEN_TTL_HOURS)
const tokenTtlHours = parseInteger(process.env.CLAIM_TOKEN_TTL_HOURS);
if (tokenTtlHours !== null) {
nextConfig.orders.tokenTtlHours = tokenTtlHours
nextConfig.orders.tokenTtlHours = tokenTtlHours;
}
const adminSessionSecret = String(process.env.ADMIN_SESSION_SECRET || '').trim()
const adminSessionSecret = String(
process.env.ADMIN_SESSION_SECRET || ""
).trim();
if (adminSessionSecret) {
nextConfig.admin.sessionSecret = adminSessionSecret
nextConfig.admin.sessionSecret = adminSessionSecret;
}
const adminSessionTtlHours = parseInteger(process.env.ADMIN_SESSION_TTL_HOURS)
const adminSessionTtlHours = parseInteger(
process.env.ADMIN_SESSION_TTL_HOURS
);
if (adminSessionTtlHours !== null) {
nextConfig.admin.sessionTtlHours = adminSessionTtlHours
nextConfig.admin.sessionTtlHours = adminSessionTtlHours;
}
const adminDefaultUsers = parseJsonArray(process.env.ADMIN_DEFAULT_USERS_JSON)
const adminDefaultUsers = parseJsonArray(
process.env.ADMIN_DEFAULT_USERS_JSON
);
if (adminDefaultUsers) {
nextConfig.admin.defaultUsers = adminDefaultUsers
nextConfig.admin.defaultUsers = adminDefaultUsers;
}
const agisoAppSecret = String(process.env.AGISO_APP_SECRET || '').trim()
const agisoAppSecret = String(process.env.AGISO_APP_SECRET || "").trim();
if (agisoAppSecret) {
nextConfig.platforms.agiso.appSecret = agisoAppSecret
nextConfig.platforms.agiso.appSecret = agisoAppSecret;
}
const agisoTradeDetailEndpoint = String(process.env.AGISO_TRADE_DETAIL_ENDPOINT || '').trim()
const agisoTradeDetailEndpoint = String(
process.env.AGISO_TRADE_DETAIL_ENDPOINT || ""
).trim();
if (agisoTradeDetailEndpoint) {
nextConfig.platforms.agiso.tradeDetail.endpoint = agisoTradeDetailEndpoint
nextConfig.platforms.agiso.tradeDetail.endpoint = agisoTradeDetailEndpoint;
}
const agisoTradeDetailApiVersion = String(process.env.AGISO_TRADE_DETAIL_API_VERSION || '').trim()
const agisoTradeDetailApiVersion = String(
process.env.AGISO_TRADE_DETAIL_API_VERSION || ""
).trim();
if (agisoTradeDetailApiVersion) {
nextConfig.platforms.agiso.tradeDetail.apiVersion = agisoTradeDetailApiVersion
nextConfig.platforms.agiso.tradeDetail.apiVersion =
agisoTradeDetailApiVersion;
}
const agisoTradeDetailTimeoutMs = parseInteger(process.env.AGISO_TRADE_DETAIL_TIMEOUT_MS)
const agisoTradeDetailTimeoutMs = parseInteger(
process.env.AGISO_TRADE_DETAIL_TIMEOUT_MS
);
if (agisoTradeDetailTimeoutMs !== null) {
nextConfig.platforms.agiso.tradeDetail.timeoutMs = agisoTradeDetailTimeoutMs
nextConfig.platforms.agiso.tradeDetail.timeoutMs =
agisoTradeDetailTimeoutMs;
}
const agisoAutoDeliveryEnabled = parseBoolean(process.env.AGISO_AUTO_DELIVERY_ENABLED)
const agisoAutoDeliveryEnabled = parseBoolean(
process.env.AGISO_AUTO_DELIVERY_ENABLED
);
if (agisoAutoDeliveryEnabled !== null) {
nextConfig.platforms.agiso.autoDelivery.enabled = agisoAutoDeliveryEnabled
nextConfig.platforms.agiso.autoDelivery.enabled = agisoAutoDeliveryEnabled;
}
const agisoAutoDeliveryEndpoint = String(process.env.AGISO_AUTO_DELIVERY_ENDPOINT || '').trim()
const agisoAutoDeliveryEndpoint = String(
process.env.AGISO_AUTO_DELIVERY_ENDPOINT || ""
).trim();
if (agisoAutoDeliveryEndpoint) {
nextConfig.platforms.agiso.autoDelivery.endpoint = agisoAutoDeliveryEndpoint
nextConfig.platforms.agiso.autoDelivery.endpoint =
agisoAutoDeliveryEndpoint;
}
const agisoAutoDeliveryApiVersion = String(process.env.AGISO_AUTO_DELIVERY_API_VERSION || '').trim()
const agisoAutoDeliveryApiVersion = String(
process.env.AGISO_AUTO_DELIVERY_API_VERSION || ""
).trim();
if (agisoAutoDeliveryApiVersion) {
nextConfig.platforms.agiso.autoDelivery.apiVersion = agisoAutoDeliveryApiVersion
nextConfig.platforms.agiso.autoDelivery.apiVersion =
agisoAutoDeliveryApiVersion;
}
const agisoMessageApiVersion = String(process.env.AGISO_MESSAGE_API_VERSION || '').trim()
const agisoMessageApiVersion = String(
process.env.AGISO_MESSAGE_API_VERSION || ""
).trim();
if (agisoMessageApiVersion) {
nextConfig.platforms.agiso.messaging.apiVersion = agisoMessageApiVersion
nextConfig.platforms.agiso.messaging.apiVersion = agisoMessageApiVersion;
}
const agisoSendMessageEndpoint = String(process.env.AGISO_SEND_MESSAGE_ENDPOINT || '').trim()
const agisoSendMessageEndpoint = String(
process.env.AGISO_SEND_MESSAGE_ENDPOINT || ""
).trim();
if (agisoSendMessageEndpoint) {
nextConfig.platforms.agiso.messaging.sendMessageEndpoint = agisoSendMessageEndpoint
nextConfig.platforms.agiso.messaging.sendMessageEndpoint =
agisoSendMessageEndpoint;
}
const agisoMessagingEnabled = parseBoolean(process.env.AGISO_MESSAGING_ENABLED)
const agisoMessagingEnabled = parseBoolean(
process.env.AGISO_MESSAGING_ENABLED
);
if (agisoMessagingEnabled !== null) {
nextConfig.platforms.agiso.messaging.enabled = agisoMessagingEnabled
nextConfig.platforms.agiso.messaging.enabled = agisoMessagingEnabled;
}
const ninetyoneUserId = String(process.env.NINETYONE_USER_ID || '').trim()
const ninetyoneUserId = String(process.env.NINETYONE_USER_ID || "").trim();
if (ninetyoneUserId) {
nextConfig.platforms.ninetyone.userId = ninetyoneUserId
nextConfig.platforms.ninetyone.userId = ninetyoneUserId;
}
const ninetyoneSecret = String(process.env.NINETYONE_SECRET || '').trim()
const ninetyoneSecret = String(process.env.NINETYONE_SECRET || "").trim();
if (ninetyoneSecret) {
nextConfig.platforms.ninetyone.secret = ninetyoneSecret
nextConfig.platforms.ninetyone.secret = ninetyoneSecret;
}
const ninetyoneVersion = String(process.env.NINETYONE_VERSION || '').trim()
const ninetyoneVersion = String(process.env.NINETYONE_VERSION || "").trim();
if (ninetyoneVersion) {
nextConfig.platforms.ninetyone.version = ninetyoneVersion
nextConfig.platforms.ninetyone.version = ninetyoneVersion;
}
const ninetyoneShopId = String(process.env.NINETYONE_SHOP_ID || '').trim()
const ninetyoneShopId = String(process.env.NINETYONE_SHOP_ID || "").trim();
if (ninetyoneShopId) {
nextConfig.platforms.ninetyone.shopId = ninetyoneShopId
nextConfig.platforms.ninetyone.shopId = ninetyoneShopId;
}
const ninetyoneShopName = String(process.env.NINETYONE_SHOP_NAME || '').trim()
const ninetyoneShopName = String(
process.env.NINETYONE_SHOP_NAME || ""
).trim();
if (ninetyoneShopName) {
nextConfig.platforms.ninetyone.shopName = ninetyoneShopName
nextConfig.platforms.ninetyone.shopName = ninetyoneShopName;
}
const ninetyoneTimestampToleranceSeconds = parseInteger(process.env.NINETYONE_TIMESTAMP_TOLERANCE_SECONDS)
const ninetyoneTimestampToleranceSeconds = parseInteger(
process.env.NINETYONE_TIMESTAMP_TOLERANCE_SECONDS
);
if (ninetyoneTimestampToleranceSeconds !== null) {
nextConfig.platforms.ninetyone.timestampToleranceSeconds = ninetyoneTimestampToleranceSeconds
nextConfig.platforms.ninetyone.timestampToleranceSeconds =
ninetyoneTimestampToleranceSeconds;
}
const ninetyoneCardsEncoding = String(process.env.NINETYONE_CARDS_ENCODING || '').trim()
const ninetyoneCardsEncoding = String(
process.env.NINETYONE_CARDS_ENCODING || ""
).trim();
if (ninetyoneCardsEncoding) {
nextConfig.platforms.ninetyone.cardsEncoding = ninetyoneCardsEncoding
nextConfig.platforms.ninetyone.cardsEncoding = ninetyoneCardsEncoding;
}
const cloudtentaclesBaseUrl = String(process.env.CLOUDTENTACLES_BASE_URL || '').trim()
const cloudtentaclesBaseUrl = String(
process.env.CLOUDTENTACLES_BASE_URL || ""
).trim();
if (cloudtentaclesBaseUrl) {
nextConfig.platforms.cloudtentacles.baseUrl = cloudtentaclesBaseUrl
nextConfig.platforms.cloudtentacles.baseUrl = cloudtentaclesBaseUrl;
}
const cloudtentaclesTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_TIMEOUT_MS)
const cloudtentaclesTimeoutMs = parseInteger(
process.env.CLOUDTENTACLES_TIMEOUT_MS
);
if (cloudtentaclesTimeoutMs !== null) {
nextConfig.platforms.cloudtentacles.timeoutMs = cloudtentaclesTimeoutMs
nextConfig.platforms.cloudtentacles.timeoutMs = cloudtentaclesTimeoutMs;
}
const cloudtentaclesSendSmsPath = String(process.env.CLOUDTENTACLES_SEND_SMS_PATH || '').trim()
const cloudtentaclesSendSmsPath = String(
process.env.CLOUDTENTACLES_SEND_SMS_PATH || ""
).trim();
if (cloudtentaclesSendSmsPath) {
nextConfig.platforms.cloudtentacles.sendSmsPath = cloudtentaclesSendSmsPath
nextConfig.platforms.cloudtentacles.sendSmsPath = cloudtentaclesSendSmsPath;
}
const cloudtentaclesLoginPath = String(process.env.CLOUDTENTACLES_LOGIN_PATH || '').trim()
const cloudtentaclesLoginPath = String(
process.env.CLOUDTENTACLES_LOGIN_PATH || ""
).trim();
if (cloudtentaclesLoginPath) {
nextConfig.platforms.cloudtentacles.loginPath = cloudtentaclesLoginPath
nextConfig.platforms.cloudtentacles.loginPath = cloudtentaclesLoginPath;
}
const cloudtentaclesUserInfoPath = String(process.env.CLOUDTENTACLES_USER_INFO_PATH || '').trim()
const cloudtentaclesUserInfoPath = String(
process.env.CLOUDTENTACLES_USER_INFO_PATH || ""
).trim();
if (cloudtentaclesUserInfoPath) {
nextConfig.platforms.cloudtentacles.userInfoPath = cloudtentaclesUserInfoPath
nextConfig.platforms.cloudtentacles.userInfoPath =
cloudtentaclesUserInfoPath;
}
const cloudtentaclesAssetPath = String(process.env.CLOUDTENTACLES_ASSET_PATH || '').trim()
const cloudtentaclesAssetPath = String(
process.env.CLOUDTENTACLES_ASSET_PATH || ""
).trim();
if (cloudtentaclesAssetPath) {
nextConfig.platforms.cloudtentacles.assetPath = cloudtentaclesAssetPath
nextConfig.platforms.cloudtentacles.assetPath = cloudtentaclesAssetPath;
}
const cloudtentaclesPermissionPath = String(process.env.CLOUDTENTACLES_PERMISSION_PATH || '').trim()
const cloudtentaclesPermissionPath = String(
process.env.CLOUDTENTACLES_PERMISSION_PATH || ""
).trim();
if (cloudtentaclesPermissionPath) {
nextConfig.platforms.cloudtentacles.permissionPath = cloudtentaclesPermissionPath
nextConfig.platforms.cloudtentacles.permissionPath =
cloudtentaclesPermissionPath;
}
const cloudtentaclesCategoriesPath = String(process.env.CLOUDTENTACLES_CATEGORIES_PATH || '').trim()
const cloudtentaclesCategoriesPath = String(
process.env.CLOUDTENTACLES_CATEGORIES_PATH || ""
).trim();
if (cloudtentaclesCategoriesPath) {
nextConfig.platforms.cloudtentacles.categoriesPath = cloudtentaclesCategoriesPath
nextConfig.platforms.cloudtentacles.categoriesPath =
cloudtentaclesCategoriesPath;
}
const cloudtentaclesSkuListPath = String(process.env.CLOUDTENTACLES_SKU_LIST_PATH || '').trim()
const cloudtentaclesSkuListPath = String(
process.env.CLOUDTENTACLES_SKU_LIST_PATH || ""
).trim();
if (cloudtentaclesSkuListPath) {
nextConfig.platforms.cloudtentacles.skuListPath = cloudtentaclesSkuListPath
nextConfig.platforms.cloudtentacles.skuListPath = cloudtentaclesSkuListPath;
}
const cloudtentaclesSkuBuyPath = String(process.env.CLOUDTENTACLES_SKU_BUY_PATH || '').trim()
const cloudtentaclesSkuBuyPath = String(
process.env.CLOUDTENTACLES_SKU_BUY_PATH || ""
).trim();
if (cloudtentaclesSkuBuyPath) {
nextConfig.platforms.cloudtentacles.skuBuyPath = cloudtentaclesSkuBuyPath
nextConfig.platforms.cloudtentacles.skuBuyPath = cloudtentaclesSkuBuyPath;
}
const cloudtentaclesKnapsackPath = String(process.env.CLOUDTENTACLES_KNAPSACK_PATH || '').trim()
const cloudtentaclesKnapsackPath = String(
process.env.CLOUDTENTACLES_KNAPSACK_PATH || ""
).trim();
if (cloudtentaclesKnapsackPath) {
nextConfig.platforms.cloudtentacles.knapsackPath = cloudtentaclesKnapsackPath
nextConfig.platforms.cloudtentacles.knapsackPath =
cloudtentaclesKnapsackPath;
}
const cloudtentaclesVnListPath = String(process.env.CLOUDTENTACLES_VN_LIST_PATH || '').trim()
const cloudtentaclesVnListPath = String(
process.env.CLOUDTENTACLES_VN_LIST_PATH || ""
).trim();
if (cloudtentaclesVnListPath) {
nextConfig.platforms.cloudtentacles.vnListPath = cloudtentaclesVnListPath
nextConfig.platforms.cloudtentacles.vnListPath = cloudtentaclesVnListPath;
}
const cloudtentaclesVnAppointPath = String(process.env.CLOUDTENTACLES_VN_APPOINT_PATH || '').trim()
const cloudtentaclesVnAppointPath = String(
process.env.CLOUDTENTACLES_VN_APPOINT_PATH || ""
).trim();
if (cloudtentaclesVnAppointPath) {
nextConfig.platforms.cloudtentacles.vnAppointPath = cloudtentaclesVnAppointPath
nextConfig.platforms.cloudtentacles.vnAppointPath =
cloudtentaclesVnAppointPath;
}
const cloudtentaclesVnGenerateLoginCodePath = String(process.env.CLOUDTENTACLES_VN_GENERATE_LOGIN_CODE_PATH || '').trim()
const cloudtentaclesVnGenerateLoginCodePath = String(
process.env.CLOUDTENTACLES_VN_GENERATE_LOGIN_CODE_PATH || ""
).trim();
if (cloudtentaclesVnGenerateLoginCodePath) {
nextConfig.platforms.cloudtentacles.vnGenerateLoginCodePath = cloudtentaclesVnGenerateLoginCodePath
nextConfig.platforms.cloudtentacles.vnGenerateLoginCodePath =
cloudtentaclesVnGenerateLoginCodePath;
}
const cloudtentaclesVnVerifCodePath = String(process.env.CLOUDTENTACLES_VN_VERIF_CODE_PATH || '').trim()
const cloudtentaclesVnVerifCodePath = String(
process.env.CLOUDTENTACLES_VN_VERIF_CODE_PATH || ""
).trim();
if (cloudtentaclesVnVerifCodePath) {
nextConfig.platforms.cloudtentacles.vnVerifCodePath = cloudtentaclesVnVerifCodePath
nextConfig.platforms.cloudtentacles.vnVerifCodePath =
cloudtentaclesVnVerifCodePath;
}
const cloudtentaclesVnVerifyLoginCodePath = String(process.env.CLOUDTENTACLES_VN_VERIFY_LOGIN_CODE_PATH || '').trim()
const cloudtentaclesVnVerifyLoginCodePath = String(
process.env.CLOUDTENTACLES_VN_VERIFY_LOGIN_CODE_PATH || ""
).trim();
if (cloudtentaclesVnVerifyLoginCodePath) {
nextConfig.platforms.cloudtentacles.vnVerifyLoginCodePath = cloudtentaclesVnVerifyLoginCodePath
nextConfig.platforms.cloudtentacles.vnVerifyLoginCodePath =
cloudtentaclesVnVerifyLoginCodePath;
}
const cloudtentaclesVnBindUrlPath = String(process.env.CLOUDTENTACLES_VN_BIND_URL_PATH || '').trim()
const cloudtentaclesVnBindUrlPath = String(
process.env.CLOUDTENTACLES_VN_BIND_URL_PATH || ""
).trim();
if (cloudtentaclesVnBindUrlPath) {
nextConfig.platforms.cloudtentacles.vnBindUrlPath = cloudtentaclesVnBindUrlPath
nextConfig.platforms.cloudtentacles.vnBindUrlPath =
cloudtentaclesVnBindUrlPath;
}
const cloudtentaclesVnBindInfoPath = String(process.env.CLOUDTENTACLES_VN_BIND_INFO_PATH || '').trim()
const cloudtentaclesVnBindInfoPath = String(
process.env.CLOUDTENTACLES_VN_BIND_INFO_PATH || ""
).trim();
if (cloudtentaclesVnBindInfoPath) {
nextConfig.platforms.cloudtentacles.vnBindInfoPath = cloudtentaclesVnBindInfoPath
nextConfig.platforms.cloudtentacles.vnBindInfoPath =
cloudtentaclesVnBindInfoPath;
}
const cloudtentaclesVnBackPath = String(process.env.CLOUDTENTACLES_VN_BACK_PATH || '').trim()
const cloudtentaclesVnBackPath = String(
process.env.CLOUDTENTACLES_VN_BACK_PATH || ""
).trim();
if (cloudtentaclesVnBackPath) {
nextConfig.platforms.cloudtentacles.vnBackPath = cloudtentaclesVnBackPath
nextConfig.platforms.cloudtentacles.vnBackPath = cloudtentaclesVnBackPath;
}
const cloudtentaclesBindUrlTtlSeconds = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_TTL_SECONDS)
const cloudtentaclesBindUrlTtlSeconds = parseInteger(
process.env.CLOUDTENTACLES_BIND_URL_TTL_SECONDS
);
if (cloudtentaclesBindUrlTtlSeconds !== null) {
nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds = cloudtentaclesBindUrlTtlSeconds
nextConfig.platforms.cloudtentacles.bindUrlTtlSeconds =
cloudtentaclesBindUrlTtlSeconds;
}
const cloudtentaclesBindUrlProbeIntervalSeconds = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS)
const cloudtentaclesBindUrlProbeIntervalSeconds = parseInteger(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS
);
if (cloudtentaclesBindUrlProbeIntervalSeconds !== null) {
nextConfig.platforms.cloudtentacles.bindUrlProbeIntervalSeconds = cloudtentaclesBindUrlProbeIntervalSeconds
nextConfig.platforms.cloudtentacles.bindUrlProbeIntervalSeconds =
cloudtentaclesBindUrlProbeIntervalSeconds;
}
const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS)
const cloudtentaclesBindUrlProbeTimeoutMs = parseInteger(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS
);
if (cloudtentaclesBindUrlProbeTimeoutMs !== null) {
nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs = cloudtentaclesBindUrlProbeTimeoutMs
nextConfig.platforms.cloudtentacles.bindUrlProbeTimeoutMs =
cloudtentaclesBindUrlProbeTimeoutMs;
}
const cloudtentaclesBindUrlProbeUserAgent = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT || '').trim()
const cloudtentaclesBindUrlProbeUserAgent = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT || ""
).trim();
if (cloudtentaclesBindUrlProbeUserAgent) {
nextConfig.platforms.cloudtentacles.bindUrlProbeUserAgent = cloudtentaclesBindUrlProbeUserAgent
nextConfig.platforms.cloudtentacles.bindUrlProbeUserAgent =
cloudtentaclesBindUrlProbeUserAgent;
}
const cloudtentaclesBindUrlProbeEndpoint = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT || '').trim()
const cloudtentaclesBindUrlProbeEndpoint = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT || ""
).trim();
if (cloudtentaclesBindUrlProbeEndpoint) {
nextConfig.platforms.cloudtentacles.bindUrlProbeEndpoint = cloudtentaclesBindUrlProbeEndpoint
nextConfig.platforms.cloudtentacles.bindUrlProbeEndpoint =
cloudtentaclesBindUrlProbeEndpoint;
}
const cloudtentaclesBindUrlProbeChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID || '').trim()
const cloudtentaclesBindUrlProbeChartId = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID || ""
).trim();
if (cloudtentaclesBindUrlProbeChartId) {
nextConfig.platforms.cloudtentacles.bindUrlProbeChartId = cloudtentaclesBindUrlProbeChartId
nextConfig.platforms.cloudtentacles.bindUrlProbeChartId =
cloudtentaclesBindUrlProbeChartId;
}
const cloudtentaclesBindUrlProbeSubChartId = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID || '').trim()
const cloudtentaclesBindUrlProbeSubChartId = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID || ""
).trim();
if (cloudtentaclesBindUrlProbeSubChartId) {
nextConfig.platforms.cloudtentacles.bindUrlProbeSubChartId = cloudtentaclesBindUrlProbeSubChartId
nextConfig.platforms.cloudtentacles.bindUrlProbeSubChartId =
cloudtentaclesBindUrlProbeSubChartId;
}
const cloudtentaclesBindUrlProbeIdeToken = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN || '').trim()
const cloudtentaclesBindUrlProbeIdeToken = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN || ""
).trim();
if (cloudtentaclesBindUrlProbeIdeToken) {
nextConfig.platforms.cloudtentacles.bindUrlProbeIdeToken = cloudtentaclesBindUrlProbeIdeToken
nextConfig.platforms.cloudtentacles.bindUrlProbeIdeToken =
cloudtentaclesBindUrlProbeIdeToken;
}
const cloudtentaclesBindUrlProbeActivityUrl = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL || '').trim()
const cloudtentaclesBindUrlProbeActivityUrl = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL || ""
).trim();
if (cloudtentaclesBindUrlProbeActivityUrl) {
nextConfig.platforms.cloudtentacles.bindUrlProbeActivityUrl = cloudtentaclesBindUrlProbeActivityUrl
nextConfig.platforms.cloudtentacles.bindUrlProbeActivityUrl =
cloudtentaclesBindUrlProbeActivityUrl;
}
const cloudtentaclesBindUrlProbeReferer = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_REFERER || '').trim()
const cloudtentaclesBindUrlProbeReferer = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_REFERER || ""
).trim();
if (cloudtentaclesBindUrlProbeReferer) {
nextConfig.platforms.cloudtentacles.bindUrlProbeReferer = cloudtentaclesBindUrlProbeReferer
nextConfig.platforms.cloudtentacles.bindUrlProbeReferer =
cloudtentaclesBindUrlProbeReferer;
}
const cloudtentaclesBindUrlProbeExtraCookie = String(process.env.CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE || '').trim()
const cloudtentaclesBindUrlProbeExtraCookie = String(
process.env.CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE || ""
).trim();
if (cloudtentaclesBindUrlProbeExtraCookie) {
nextConfig.platforms.cloudtentacles.bindUrlProbeExtraCookie = cloudtentaclesBindUrlProbeExtraCookie
nextConfig.platforms.cloudtentacles.bindUrlProbeExtraCookie =
cloudtentaclesBindUrlProbeExtraCookie;
}
const cloudtentaclesPublicKeyPem = String(process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || '').trim()
const cloudtentaclesPublicKeyPem = String(
process.env.CLOUDTENTACLES_PUBLIC_KEY_PEM || ""
).trim();
if (cloudtentaclesPublicKeyPem) {
nextConfig.platforms.cloudtentacles.publicKeyPem = cloudtentaclesPublicKeyPem
nextConfig.platforms.cloudtentacles.publicKeyPem =
cloudtentaclesPublicKeyPem;
}
const cloudtentaclesClientSource = String(process.env.CLOUDTENTACLES_CLIENT_SOURCE || '').trim()
const cloudtentaclesClientSource = String(
process.env.CLOUDTENTACLES_CLIENT_SOURCE || ""
).trim();
if (cloudtentaclesClientSource) {
nextConfig.platforms.cloudtentacles.clientSource = cloudtentaclesClientSource
nextConfig.platforms.cloudtentacles.clientSource =
cloudtentaclesClientSource;
}
const cloudtentaclesDeviceId = String(process.env.CLOUDTENTACLES_DEVICE_ID || '').trim()
const cloudtentaclesDeviceId = String(
process.env.CLOUDTENTACLES_DEVICE_ID || ""
).trim();
if (cloudtentaclesDeviceId) {
nextConfig.platforms.cloudtentacles.deviceId = cloudtentaclesDeviceId
nextConfig.platforms.cloudtentacles.deviceId = cloudtentaclesDeviceId;
}
const cloudtentaclesDeviceType = parseInteger(process.env.CLOUDTENTACLES_DEVICE_TYPE)
const cloudtentaclesDeviceType = parseInteger(
process.env.CLOUDTENTACLES_DEVICE_TYPE
);
if (cloudtentaclesDeviceType !== null) {
nextConfig.platforms.cloudtentacles.deviceType = cloudtentaclesDeviceType
nextConfig.platforms.cloudtentacles.deviceType = cloudtentaclesDeviceType;
}
const proofMode = String(process.env.TENCENT_REDEEM_PROOF_MODE || '').trim()
const proofMode = String(process.env.TENCENT_REDEEM_PROOF_MODE || "").trim();
if (proofMode) {
nextConfig.redeem.proofMode = proofMode
nextConfig.redeem.proofMode = proofMode;
}
return nextConfig
return nextConfig;
}
function deepMerge(baseValue, overrideValue) {
if (!isPlainObject(baseValue)) {
return cloneValue(overrideValue)
return cloneValue(overrideValue);
}
const result = cloneValue(baseValue)
const result = cloneValue(baseValue);
if (!isPlainObject(overrideValue)) {
return result
return result;
}
for (const [key, value] of Object.entries(overrideValue)) {
if (isPlainObject(value) && isPlainObject(result[key])) {
result[key] = deepMerge(result[key], value)
continue
result[key] = deepMerge(result[key], value);
continue;
}
result[key] = cloneValue(value)
result[key] = cloneValue(value);
}
return result
return result;
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((item) => cloneValue(item))
return value.map((item) => cloneValue(item));
}
if (isPlainObject(value)) {
const output = {}
const output = {};
for (const [key, item] of Object.entries(value)) {
output[key] = cloneValue(item)
output[key] = cloneValue(item);
}
return output
return output;
}
return value
return value;
}
function isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]'
return Object.prototype.toString.call(value) === "[object Object]";
}
function parseBoolean(rawValue) {
const normalized = String(rawValue || '').trim().toLowerCase()
const normalized = String(rawValue || "")
.trim()
.toLowerCase();
if (!normalized) {
return null
return null;
}
if (['1', 'true', 'yes', 'on'].includes(normalized)) {
return true
if (["1", "true", "yes", "on"].includes(normalized)) {
return true;
}
if (['0', 'false', 'no', 'off'].includes(normalized)) {
return false
if (["0", "false", "no", "off"].includes(normalized)) {
return false;
}
return null
return null;
}
function parseInteger(rawValue) {
const normalized = String(rawValue || '').trim()
const normalized = String(rawValue || "").trim();
if (!normalized) {
return null
return null;
}
const parsed = Number(normalized)
return Number.isFinite(parsed) ? parsed : null
const parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : null;
}
function parseJsonArray(rawValue) {
const normalized = String(rawValue || '').trim()
const normalized = String(rawValue || "").trim();
if (!normalized) {
return null
return null;
}
try {
const parsed = JSON.parse(normalized)
return Array.isArray(parsed) ? parsed : null
const parsed = JSON.parse(normalized);
return Array.isArray(parsed) ? parsed : null;
} catch {
return null
return null;
}
}
function normalizeBooleanLike(value) {
if (typeof value === 'boolean') {
return value
if (typeof value === "boolean") {
return value;
}
return parseBoolean(value)
return parseBoolean(value);
}
+78 -1
View File
@@ -22,14 +22,89 @@ export async function batchRecognizeTencentCaptcha(payload) {
}
export async function warmupLocalOcrWorker() {
const mode = resolveOcrMode()
if (mode === 'http') {
await warmupOcrViaHttp()
return
}
await runOcrCommand('healthcheck', { timeoutMs: 30_000 })
}
export async function closeLocalOcrWorker() {
// OCR 改为按次调用 Python 子进程,这里不再维护常驻 worker。
// OCR 改为按次调用 Python 子进程或 HTTP 服务,这里不再维护常驻 worker。
}
// ── Mode Resolution ──────────────────────────────────────────────────────────
function resolveOcrMode() {
if (String(runtimeConfig.ocr.baseUrl || '').trim()) {
return 'http'
}
return 'subprocess'
}
// ── HTTP Mode ─────────────────────────────────────────────────────────────────
async function callOcrViaHttp(action, payload, { timeoutMs }) {
const baseUrl = String(runtimeConfig.ocr.baseUrl).trim().replace(/\/+$/, '')
const url = `${baseUrl}/ocr/${action}`
let response
try {
response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(timeoutMs),
})
} catch (err) {
if (err instanceof Error && err.name === 'TimeoutError') {
throw new Error(`OCR 请求超时(${timeoutMs}ms),请检查 OCR 服务状态`)
}
throw new Error(`OCR 服务不可用(${baseUrl}),请检查 ocr-worker 容器是否正常运行`)
}
if (!response.ok) {
throw new Error(`OCR 服务返回错误:HTTP ${response.status}`)
}
const result = await response.json()
if (!result || typeof result !== 'object' || Array.isArray(result)) {
throw new Error('OCR 服务返回了无效响应')
}
if (result.code !== 0) {
throw new Error(`OCR 服务返回错误:${result.msg || '未知错误'}`)
}
return result
}
async function warmupOcrViaHttp() {
const baseUrl = String(runtimeConfig.ocr.baseUrl).trim().replace(/\/+$/, '')
let response
try {
response = await fetch(`${baseUrl}/health`, {
signal: AbortSignal.timeout(30_000),
})
} catch (err) {
if (err instanceof Error && err.name === 'TimeoutError') {
throw new Error(`OCR 服务健康检查超时,请检查 ${baseUrl} 是否可访问`)
}
throw new Error(`OCR 服务不可用(${baseUrl}),请检查 ocr-worker 容器是否正常运行`)
}
if (!response.ok) {
throw new Error(`OCR 服务健康检查失败:HTTP ${response.status}`)
}
}
// ── Request Router ────────────────────────────────────────────────────────────
async function callOcrRequest(action, payload, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
const mode = resolveOcrMode()
if (mode === 'http') {
return await callOcrViaHttp(action, payload, { timeoutMs })
}
// subprocess mode
const response = await runOcrCommand('request', {
timeoutMs,
input: JSON.stringify({
@@ -45,6 +120,8 @@ async function callOcrRequest(action, payload, { timeoutMs = DEFAULT_TIMEOUT_MS
return response
}
// ── Subprocess Mode ───────────────────────────────────────────────────────────
async function runOcrCommand(command, { timeoutMs = DEFAULT_TIMEOUT_MS, input = '' } = {}) {
const projectRoot = resolveOcrProjectRoot()
const workerCommand = resolveWorkerCommand(projectRoot)
+2 -1
View File
@@ -1,6 +1,6 @@
// @ts-check
export {}
export {};
/**
* @typedef {{
@@ -46,6 +46,7 @@ export {}
* }
* ocr: {
* projectRoot: string
* baseUrl: string
* }
* data: {
* root: string