方案 | 是否需要安装 | 适用场景 |
axios(本文示例默认) | npm install axios | 已有项目在用 axios,或偏好其 API 风格。 |
Node.js 原生 fetch | 无需安装(Node.js ≥ 18 内置) | 零依赖、现代项目推荐。 |
Node.js 原生 https | 无需安装 | 兼容老版本 Node.js(< 18)。 |
npm install axios
// 替换 axios.post 调用// 原: const resp = await axios.post(`https://${MPS_HOST}`, payload, { headers });// return resp.data;// 改为:const resp = await fetch(`https://${MPS_HOST}`, {method: 'POST',headers: headers,body: JSON.stringify(payload)});return await resp.json();
crypto 模块即可完成签名,无需额外安装。签名部分无外部依赖。{"tencentCloud": {"secretId": "您的 SecretId","secretKey": "您的 SecretKey","region": "ap-guangzhou"}}
.gitignore)。/*** 腾讯云 API 签名工具 (TC3-HMAC-SHA256)*/const crypto = require('crypto');function sha256(message) {return crypto.createHash('sha256').update(message).digest('hex');}function hmac256(key, message) {return crypto.createHmac('sha256', key).update(message).digest();}/*** 生成腾讯云 API V3 签名* @param {string} secretId - 腾讯云 SecretId* @param {string} secretKey - 腾讯云 SecretKey* @param {string} service - 服务名,如 'mps'* @param {string} action - 接口名,如 'CreateAigcVideoTask'* @param {string} payload - 请求体 JSON 字符串* @param {string} region - 地域,如 'ap-guangzhou'* @param {string} [version] - API 版本号,默认 '2019-06-12'* @returns {{ headers: object }} - 包含完整签名的请求头*/function signRequest(secretId, secretKey, service, action, payload, region, version) {const timestamp = Math.floor(Date.now() / 1000);const date = new Date(timestamp * 1000).toISOString().split('T')[0];// ===== 步骤1: 拼接规范请求串 =====const httpRequestMethod = 'POST';const canonicalUri = '/';const canonicalQueryString = '';const contentType = 'application/json';const canonicalHeaders =`content-type:${contentType}\\n` +`host:${service}.tencentcloudapi.com\\n` +`x-tc-action:${action.toLowerCase()}\\n`;const signedHeaders = 'content-type;host;x-tc-action';const hashedRequestPayload = sha256(payload);const canonicalRequest =`${httpRequestMethod}\\n${canonicalUri}\\n${canonicalQueryString}\\n` +`${canonicalHeaders}\\n${signedHeaders}\\n${hashedRequestPayload}`;// ===== 步骤2: 拼接待签名字符串 =====const algorithm = 'TC3-HMAC-SHA256';const credentialScope = `${date}/${service}/tc3_request`;const hashedCanonicalRequest = sha256(canonicalRequest);const stringToSign =`${algorithm}\\n${timestamp}\\n${credentialScope}\\n${hashedCanonicalRequest}`;// ===== 步骤3: 计算签名 =====const secretDate = hmac256(`TC3${secretKey}`, date);const secretService = hmac256(secretDate, service);const secretSigning = hmac256(secretService, 'tc3_request');const signature = crypto.createHmac('sha256', secretSigning).update(stringToSign).digest('hex');// ===== 步骤4: 拼接 Authorization =====const authorization =`${algorithm} Credential=${secretId}/${credentialScope}, ` +`SignedHeaders=${signedHeaders}, Signature=${signature}`;return {headers: {'Authorization': authorization,'Content-Type': contentType,'Host': `${service}.tencentcloudapi.com`,'X-TC-Action': action,'X-TC-Timestamp': String(timestamp),'X-TC-Version': version || '2019-06-12','X-TC-Region': region || ''}};}module.exports = { signRequest };
// mps-api.js — 混元 3D 生成 API 封装(Node.js ≥ 18,原生 fetch,零依赖)const { signRequest } = require('./sign');const SERVICE = 'mps';const VERSION = '2019-06-12';const REGION = 'ap-guangzhou';const ENDPOINT = 'mps.intl.tencentcloudapi.com';const SECRET_ID = process.env.TENCENTCLOUD_SECRET_ID;const SECRET_KEY = process.env.TENCENTCLOUD_SECRET_KEY;/** 通用调用:签名 → 发请求 → 校验错误 → 返回 Response */async function callMpsApi(action, params) {const payload = params || {};const { authorization, timestamp } = signRequest({secretId: SECRET_ID,secretKey: SECRET_KEY,action,payload,});const res = await fetch('https://' + ENDPOINT + '/', {method: 'POST',headers: {Authorization: authorization,'Content-Type': 'application/json','X-TC-Action': action,'X-TC-Timestamp': String(timestamp),'X-TC-Version': VERSION,'X-TC-Region': REGION,},body: JSON.stringify(payload),});const data = await res.json();const r = data.Response || {};// 业务错误:先看标准错误信封 Error.Code,再看扁平 ErrorCode(如 ResourceNotFound.TaskId)if (r.Error || r.ErrorCode) {const code = (r.Error && r.Error.Code) || r.ErrorCode;const message = (r.Error && r.Error.Message) || r.ErrorMessage;throw new Error(`${action} failed: ${code} - ${message} (RequestId: ${r.RequestId})`);}return r;}/*** 提交 3D 生成任务(文生 / 图生 / 多视角图生,入参三选一,互斥)* @param {object} params* @param {string} [params.Prompt] 文生 3D 提示词,最长 1024 utf-8 字符* @param {string} [params.ImageUrl] 图生 3D 参考图 URL(jpg/jpeg/png/bmp/webp,* 短边 ≥ 512、长边 ≤ 4096、建议 ≤ 10MB,须公网可访问)* @param {Array<{ViewType: string, ViewImageUrl: string}>} [params.MultiViewImages]* 多视角图生 3D:2~8 张,必须包含 front 视角,ViewType 不可重复。* 可选值:front / back / left / right / top / bottom / left_front / right_front* @param {string} [params.GenerateType='Normal'] Normal:完整 3D 资产(几何+纹理);* Geometry:仅几何(更快,约 40s)* @param {boolean} [params.EnablePBR=false] 是否输出 PBR 材质* @param {number} [params.FaceCount=500000] 面片数,范围 [3000, 1500000]* @returns {Promise<{TaskId: string, RequestId: string}>}* TaskId:任务唯一 ID,用于后续查询;RequestId:请求追踪 ID,定位问题请提供此 ID*/async function submitHunyuan3DTask(params) {return callMpsApi('SubmitHunyuan3DTask', params);}/*** 查询 3D 生成任务* @param {string} taskId Submit 返回的任务 ID* @returns {Promise<{* Status: 'WAIT' | 'RUN' | 'DONE' | 'FAIL',* Progress: number,* ErrorCode?: string, // 仅 FAIL 时返回,如 InternalError.ModelInference* ErrorMessage?: string, // 仅 FAIL 时返回* ResultFile3Ds?: Array<{ // 仅 DONE 时返回* Type: 'OBJ' | 'GLB' | 'MTL' | 'OBJ_ZIP',* Url: string,* PreviewImageUrl?: string* }>,* RequestId: string* }>}* ⚠️ ResultFile3Ds 中的 Url 为临时签名 URL,有效期约 24 小时,请及时下载或转存。* 默认输出 OBJ + GLB 两种格式,其中 OBJ 同时提供单独文件和含 MTL+纹理的 ZIP 包。*/async function queryHunyuan3DTask(taskId) {return callMpsApi('QueryHunyuan3DTask', { TaskId: taskId });}module.exports = { submitHunyuan3DTask, queryHunyuan3DTask };
const { submitHunyuan3DTask, queryHunyuan3DTask } = require('./mps-api');async function main() {// ① 提交任务const submitResp = await submitHunyuan3DTask({Prompt: 'a cute cartoon dinosaur, green, small horns',FaceCount: 500000,});const taskId = submitResp.TaskId;console.log('TaskId:', taskId);// ② 每 8 秒轮询一次while (true) {const r = await queryHunyuan3DTask(taskId);console.log('Status:', r.Status, 'Progress:', r.Progress);if (r.Status === 'DONE') {// ⚠️ 以下 URL 为临时签名 URL,有效期约 24 小时,请尽快下载或转存for (const f of r.ResultFile3Ds) {console.log(f.Type, '->', f.Url);}break;}if (r.Status === 'FAIL') {console.error('FAIL:', r.ErrorCode, r.ErrorMessage);break;}await new Promise(resolve => setTimeout(resolve, 8000));}}main().catch(console.error);
const { submitHunyuan3DTask } = require('./mps-api');// 场景 A:图生 3D(完整资产 + PBR)await submitHunyuan3DTask({ImageUrl: 'https://example.com/test.png',EnablePBR: true,});// 场景 B:图生 3D(仅几何,速度快)await submitHunyuan3DTask({ImageUrl: 'https://example.com/test.png',GenerateType: 'Geometry',FaceCount: 100000,});// 场景 C:多视角图生 3D(至少 2 张,必须包含 front 视角;质量要求高可追加 left / right)await submitHunyuan3DTask({MultiViewImages: [{ ViewType: 'front', ViewImageUrl: 'https://example.com/front.png' },{ ViewType: 'back', ViewImageUrl: 'https://example.com/back.png' },],EnablePBR: true,});// 场景 D:多视角图生几何await submitHunyuan3DTask({MultiViewImages: [{ ViewType: 'front', ViewImageUrl: 'https://example.com/front.png' },{ ViewType: 'back', ViewImageUrl: 'https://example.com/back.png' },],GenerateType: 'Geometry',});
const { submitHunyuan3DTask, queryHunyuan3DTask } = require('./mps-api');/*** 通用轮询函数:带超时与间隔控制* @param {string} taskId 任务 ID* @param {object} [options]* @param {number} [options.timeoutMs=600000] 超时时间,默认 10 分钟* @param {number} [options.intervalMs=8000] 轮询间隔,默认 8 秒(不要低于 1 秒,会触发限频)* @returns {Promise<object>} DONE 状态的完整 Response*/async function pollTaskResult(taskId, options = {}) {const { timeoutMs = 10 * 60 * 1000, intervalMs = 8000 } = options;const deadline = Date.now() + timeoutMs;while (Date.now() < deadline) {const r = await queryHunyuan3DTask(taskId);if (r.Status === 'DONE') return r;if (r.Status === 'FAIL') {throw new Error(`task failed: ${r.ErrorCode} - ${r.ErrorMessage}`);}await new Promise(resolve => setTimeout(resolve, intervalMs));}throw new Error(`poll timeout after ${timeoutMs} ms, TaskId: ${taskId}`);}/*** 生产级工作流:先用 Geometry 模式快速验证骨架,满意后再生成完整资产* (仅几何约 40s,完整资产约 120s,P50 参考值)*/async function generateWithPreview(prompt) {// ① 先跑仅几何任务,快速评估模型骨架const geometryTask = await submitHunyuan3DTask({Prompt: prompt,GenerateType: 'Geometry',FaceCount: 200000,});const geometryResult = await pollTaskResult(geometryTask.TaskId);console.log('几何预览完成,文件数:', geometryResult.ResultFile3Ds.length);// ② 满意后再提交完整资产任务(几何 + 纹理 [+ PBR])const finalTask = await submitHunyuan3DTask({Prompt: prompt,GenerateType: 'Normal',FaceCount: 500000,EnablePBR: true,});const finalResult = await pollTaskResult(finalTask.TaskId);// ⚠️ 收到 DONE 后立即下载转存(URL 约 24 小时过期)return finalResult.ResultFile3Ds; // OBJ / GLB 文件列表}
{"tencentCloud": {"secretId": "TENCENTCLOUD_SECRET_ID","secretKey": "TENCENTCLOUD_SECRET_KEY","region": "ap-guangzhou"},"hunyuan3d": {"generateType": "Normal","enablePBR": false,"faceCount": 500000,"multiView": {"enabled": false,"minViews": 2,"maxViews": 8,"requiredViewType": "front"}},"concurrency": {"maxConcurrentTasks": 1,"pollIntervalSeconds": 8,"pollTimeoutMinutes": 10}}
错误码 | 说明 |
InvalidParameter.NoInputSpecified | Prompt / ImageUrl / MultiViewImages 三者都未传。 |
InvalidParameter.PromptImageConflict | Prompt 与 ImageUrl / MultiViewImages 同时提供。 |
InvalidParameter.MultiInputConflict | ImageUrl 与 MultiViewImages 同时提供。 |
InvalidParameter.MissingFrontView | MultiViewImages 未包含 front 视角。 |
InvalidParameter.InsufficientViews | MultiViewImages 数量少于2。 |
InvalidParameter.DuplicateViewType | 出现重复的 ViewType。 |
InvalidParameter.FaceCountOutOfRange | FaceCount 不在 [3000, 1500000]。 |
InvalidParameter.PromptTooLong | Prompt 超过 1024 utf-8 字符。 |
错误码 | 原因 |
AuthFailure.SignatureExpire | 本机时间与腾讯云服务器偏差 > 5 分钟,请校准 NTP。 |
AuthFailure.SecretIdNotFound | SecretId 不存在或已删除。 |
AuthFailure.UnauthorizedOperation / UnauthorizedOperation | 主账号 AppId 尚未在混元 3D 服务白名单内(联系商务开通),或子账号未绑定 CAM 策略 QcloudMPSFullAccess。 |
curl -X POST https://mps.intl.tencentcloudapi.com/ \\-H "Authorization: TC3-HMAC-SHA256 Credential=AKIDxxxxxxxx/2026-08-30/mps/tc3_request, SignedHeaders=content-type;host, Signature=fe5f6f..." \\-H "Content-Type: application/json" \\-H "X-TC-Action: SubmitHunyuan3DTask" \\-H "X-TC-Version: 2019-06-12" \\-H "X-TC-Timestamp: 1756543200" \\-H "X-TC-Region: ap-guangzhou" \\-d '{"Prompt": "a cute cartoon dinosaur, green, small horns","FaceCount": 500000}'
{"Response": {"TaskId": "r_44504e4b9a3b11f186b56a073b12405b","RequestId": "d344d00c-b131-45ec-8de9-829e0c427dca"}}
POST / HTTP/1.1Host: mps.intl.tencentcloudapi.comContent-Type: application/jsonX-TC-Action: QueryHunyuan3DTaskX-TC-Version: 2019-06-12{"TaskId": "r_44504e4b9a3b11f186b56a073b12405b"}
{"Response": {"Status": "DONE","Progress": 100,"ResultFile3Ds": [{"Type": "GLB","Url": "https://hunyuan-3d-1258344703.cos.ap-guangzhou.myqcloud.com/gen_tmp_test/<task_hash>/<file_hash>.glb?q-sign-algorithm=sha1&...","PreviewImageUrl": "https://hunyuan-base-prod-12583xxxx3.cos.ap-guangzhou.myqcloud.com/openapi/text2img/<preview_hash>.png?q-sign-algorithm=sha1&..."},{"Type": "OBJ","Url": "https://hunyuan-3d-12583xxxx3.cos.ap-guangzhou.myqcloud.com/gen_tmp_test/<task_hash>/<file_hash>.obj?q-sign-algorithm=sha1&..."},{"Type": "OBJ","Url": "https://hunyuan-3d-12583xxxx3.cos.ap-guangzhou.myqcloud.com/gen_tmp_test/<task_hash>/<file_hash>.zip?q-sign-algorithm=sha1&..."}],"RequestId": "8189fa91-1c54-499c-8ce3-e182ec9c19ce"}}
{"Response": {"Status": "RUN","Progress": 0,"RequestId": "f3193547-4119-48d8-aa13-b27d29b49224"}}
{"Response": {"ErrorCode": "ResourceNotFound.TaskId","ErrorMessage": "task not found or expired","RequestId": "0ba287dc-f6b3-4f93-bc3a-887e7f971f90"}}
文档反馈