| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- /**
- * 云函数:jimengManager
- * 代理即梦图片、视频、数字人、动作迁移等任务接口,避免前端暴露上游地址和 token。
- *
- * 部署后把函数 objectId 填入 src/app/services/cloud-functions.ts 的 jimeng。
- */
- const JIMENG_TOKEN = readEnv('JIMENG_TOKEN') || 'Bearer r:f0333969e312a40e4703e8fe4ed1c600';
- const JIMENG_BASE_URL = readEnv('JIMENG_BASE_URL') || 'https://server.fmode.cn/api/volcengine/jimeng';
- const PARSE_BASE_URL = readEnv('PARSE_BASE_URL') || 'https://server.fmode.cn/parse';
- const PARSE_APP_ID = readEnv('PARSE_APP_ID') || 'ncloudmaster';
- const REQUEST_TIMEOUT_MS = Number(readEnv('JIMENG_REQUEST_TIMEOUT_MS') || 60000);
- const MAX_ATTEMPTS = Math.max(1, Number(readEnv('JIMENG_MAX_ATTEMPTS') || 3));
- const ALLOWED_ENDPOINTS = new Set([
- 'getVideoV3_720p',
- 'getVideoV3_1080p',
- 'getVideoV3_Pro',
- 'getText2ImgV31',
- 'getImgV4',
- 'getDataByTask02',
- 'getOhIdentifyMain',
- 'getOhDateByTask',
- 'getOmniHuman',
- 'getActorV2',
- ]);
- async function handler(request, response) {
- try {
- const action = pickParam(request, 'action') || 'call';
- if (action === 'call') {
- const endpoint = String(pickParam(request, 'endpoint') || '').trim();
- const payload = pickParam(request, 'payload', 'data') || {};
- if (!ALLOWED_ENDPOINTS.has(endpoint)) {
- return response.json({ code: 400, success: false, error: '不支持的生成接口' });
- }
- const upstreamPayload = stripEmpty({ ...payload, token: JIMENG_TOKEN });
- const data = await postJson(`${JIMENG_BASE_URL}/${endpoint}`, upstreamPayload);
- response.json(data);
- return;
- }
- if (action === 'getWorkResult') {
- const workId = String(pickParam(request, 'workId', 'objectId') || '').trim();
- if (!workId) {
- return response.json({ code: 400, success: false, error: '缺少作品 ID' });
- }
- const data = await getJson(`${PARSE_BASE_URL}/classes/ImagineWork/${encodeURIComponent(workId)}`, {
- 'X-Parse-Application-Id': PARSE_APP_ID,
- });
- response.json({ code: 200, success: true, data });
- return;
- }
- response.json({ code: 400, success: false, error: `未知 action: ${action}` });
- } catch (error) {
- console.error('jimengManager failed:', error && error.message ? error.message : error);
- response.json({ code: 500, success: false, error: error && error.message ? error.message : '生成服务调用失败' });
- }
- }
- function pickParam(request, ...names) {
- const sources = [request.params, request.body, request];
- for (const src of sources) {
- if (!src || typeof src !== 'object') continue;
- for (const n of names) {
- const v = src[n];
- if (v !== undefined && v !== null && v !== '') return v;
- }
- }
- return null;
- }
- function stripEmpty(value) {
- if (!value || typeof value !== 'object') return value;
- const out = Array.isArray(value) ? [] : {};
- for (const [key, val] of Object.entries(value)) {
- if (val === undefined || val === null || val === '') continue;
- if (val && typeof val === 'object' && !Array.isArray(val)) {
- const nested = stripEmpty(val);
- if (Object.keys(nested).length) out[key] = nested;
- } else {
- out[key] = val;
- }
- }
- return out;
- }
- async function postJson(url, body) {
- return requestJson(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(body),
- });
- }
- async function getJson(url, headers) {
- const data = await requestJson(url, { method: 'GET', headers });
- if (data && data.success === false) throw new Error(data.error || '查询结果失败');
- return data;
- }
- async function requestJson(url, options) {
- let lastError = null;
- for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
- try {
- const r = await fetchWithTimeout(url, options, REQUEST_TIMEOUT_MS);
- const data = await readResponse(r, url, attempt);
- if (data && data.success === false && isRetryableStatus(data.code) && attempt < MAX_ATTEMPTS) {
- await sleep(backoffMs(attempt));
- continue;
- }
- return data;
- } catch (error) {
- lastError = error;
- if (!isRetryableNetworkError(error) || attempt >= MAX_ATTEMPTS) break;
- await sleep(backoffMs(attempt));
- }
- }
- const message = lastError && lastError.message ? lastError.message : 'fetch failed';
- throw new Error(`即梦上游网络请求失败:${message};url=${maskUrl(url)};attempts=${MAX_ATTEMPTS}`);
- }
- async function fetchWithTimeout(url, options, timeoutMs) {
- if (typeof AbortController === 'undefined') {
- return fetch(url, options);
- }
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), timeoutMs);
- try {
- return await fetch(url, { ...options, signal: controller.signal });
- } finally {
- clearTimeout(timer);
- }
- }
- async function readResponse(r, url, attempt) {
- const text = await r.text();
- let data = null;
- try { data = text ? JSON.parse(text) : null; } catch {}
- if (!r.ok) {
- return {
- code: r.status,
- success: false,
- error: readError(data, text) || `HTTP ${r.status}`,
- upstream: {
- status: r.status,
- url: maskUrl(url),
- attempt,
- maxAttempts: MAX_ATTEMPTS,
- },
- };
- }
- return data || { code: 500, success: false, error: '服务返回异常' };
- }
- function isRetryableStatus(status) {
- const code = Number(status || 0);
- return code === 408 || code === 429 || code >= 500;
- }
- function isRetryableNetworkError(error) {
- const message = error && error.message ? error.message : String(error || '');
- return /fetch failed|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|network|abort|timeout/i.test(message);
- }
- function backoffMs(attempt) {
- return Math.min(12000, 1200 * attempt * attempt);
- }
- function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- function maskUrl(url) {
- try {
- const u = new URL(url);
- return `${u.origin}${u.pathname}`;
- } catch {
- return String(url || '').split('?')[0];
- }
- }
- function readError(data, fallback) {
- return data?.error?.message || data?.error || data?.message || data?.msg || fallback || '';
- }
- function readEnv(name) {
- if (typeof process !== 'undefined' && process.env && process.env[name]) {
- return process.env[name];
- }
- return '';
- }
|