如何通过Autodesk Forge API将转换后的SVF文件上传至OSS存储桶?
解决Autodesk Forge SVF文件上传至OSS存储桶的问题
核心问题分析
你当前代码里的GetDerivativeManifestAsync方法返回的是衍生转换的清单数据(JSON格式),并非SVF文件的实际字节内容,这是导致上传错误的关键。要获取SVF文件,需要先通过清单定位到SVF文件的下载URL,再下载文件内容,最后上传到OSS。
正确实现步骤
- 等待转换完成:调用
TranslateAsync后,转换是异步执行的,不能立即获取文件,需通过轮询或Webhook确认转换完成。 - 获取衍生文件清单:调用
GetDerivativeManifestAsync获取所有衍生文件的元信息,从中定位SVF文件的标识。 - 获取SVF下载URL:通过
GetDerivativeUrlAsync获取SVF文件的临时下载链接。 - 下载SVF文件:用HTTP客户端下载SVF文件的字节流。
- 上传至OSS:使用Forge的
ObjectsApi将字节流上传到指定OSS存储桶。
修正后的完整代码示例
[HttpPost] [Route("/api/aps/callback/designautomation")] public async Task<IActionResult> OnCallback(string id, string outputFileName, string rootFile, [FromBody] dynamic body) { Console.WriteLine("OnCallback 1"); try { JObject bodyJson = JObject.Parse((string)body.ToString()); await _hubContext.Clients.Client(id).SendAsync("onComplete", bodyJson.ToString()); using (var httpClient = new HttpClient()) { byte[] bs = await httpClient.GetByteArrayAsync(bodyJson["reportUrl"]?.Value<string>()); string report = System.Text.Encoding.Default.GetString(bs); await _hubContext.Clients.Client(id).SendAsync("onComplete", report); } // 获取OAuth令牌 dynamic oauth = await OAuthController.GetInternalAsync(); ObjectsApi objectsApi = new ObjectsApi(); objectsApi.Configuration.AccessToken = oauth.access_token; // 获取Design Automation输出文件的下载URL ApiResponse<dynamic> res = await objectsApi.getS3DownloadURLAsyncWithHttpInfo( NickName.ToLower() + "-design", outputFileName, new Dictionary<string, object> { { "minutesExpiration", 15.0 }, { "useCdn", true } }); await _hubContext.Clients.Client(id).SendAsync("downloadResult", (string)(res.Data.url)); Console.WriteLine("Congrats!"); // 获取输出文件详情,用于后续转换 res = await objectsApi.GetObjectDetailsAsyncWithHttpInfo( NickName.ToLower() + "-design", outputFileName); DerivativesApi derivativesApi = new DerivativesApi(); derivativesApi.Configuration.AccessToken = oauth.access_token; var formats = new List<JobPayloadItem> { new JobPayloadItem (JobPayloadItem.TypeEnum.Svf, new List<JobPayloadItem.ViewsEnum> { JobPayloadItem.ViewsEnum._2d, JobPayloadItem.ViewsEnum._3d }) }; var urn = Base64Encode(res.Data.objectId); var payload = new JobPayload( new JobPayloadInput(urn, true, rootFile), new JobPayloadOutput(formats) ); // 提交转换任务 res = await derivativesApi.TranslateAsyncWithHttpInfo(payload); await _hubContext.Clients.Client(id).SendAsync("urn", (string)urn); // 轮询等待转换完成(生产环境建议用Webhook替代) bool isCompleted = false; int retryCount = 0; const int maxRetries = 20; const int delaySeconds = 10; dynamic manifest = null; while (!isCompleted && retryCount < maxRetries) { try { manifest = await derivativesApi.GetDerivativeManifestAsync(oauth.access_token, urn); string status = manifest.status; if (status.Equals("success", StringComparison.OrdinalIgnoreCase)) { isCompleted = true; } else if (status.Equals("failed", StringComparison.OrdinalIgnoreCase)) { throw new Exception("文件转换失败"); } else { await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); retryCount++; } } catch (ApiException ex) when (ex.StatusCode == 404) { // 清单尚未生成,继续等待 await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); retryCount++; } } if (!isCompleted) { throw new Exception("文件转换超时"); } // 从清单中定位SVF文件的GUID string svfGuid = null; if (manifest.derivatives != null && manifest.derivatives.Count > 0) { var svfDerivative = manifest.derivatives.FirstOrDefault(d => d.outputType.Equals("svf", StringComparison.OrdinalIgnoreCase)); if (svfDerivative != null && svfDerivative.children != null) { var svfFile = svfDerivative.children.FirstOrDefault(c => c.role.Equals("3d", StringComparison.OrdinalIgnoreCase) && c.type.Equals("svf", StringComparison.OrdinalIgnoreCase)); svfGuid = svfFile?.guid; } } if (string.IsNullOrEmpty(svfGuid)) { throw new Exception("未找到SVF文件的标识"); } // 获取SVF文件的临时下载URL string svfDownloadUrl = await derivativesApi.GetDerivativeUrlAsync(oauth.access_token, urn, svfGuid); // 下载SVF文件字节流 byte[] svfFileBytes; using (var httpClient = new HttpClient()) { svfFileBytes = await httpClient.GetByteArrayAsync(svfDownloadUrl); } // 上传至OSS存储桶 string ossBucketName = NickName.ToLower() + "-design"; string translatedFileName = "model.svf"; // 自定义目标文件名 using (var memoryStream = new MemoryStream(svfFileBytes)) { await objectsApi.UploadObjectAsync( ossBucketName, translatedFileName, svfFileBytes.Length, memoryStream, new Dictionary<string, string>(), null, "application/octet-stream" ); } await _hubContext.Clients.Client(id).SendAsync("svfUploaded", "SVF文件已成功上传至OSS"); } catch (Exception ex) { Console.WriteLine(ex.Message); await _hubContext.Clients.Client(id).SendAsync("error", ex.Message); } return Ok(); } // 辅助方法:URL安全的Base64编码 private string Base64Encode(string input) { var bytes = System.Text.Encoding.UTF8.GetBytes(input); return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_').TrimEnd('='); }
最佳实践建议
- 用Webhook替代轮询:生产环境不要用轮询等待转换完成,提交转换任务时指定Webhook URL,Forge会在转换完成后主动推送通知,提升效率。
- 大文件分块上传:如果SVF文件超过100MB,使用
UploadObjectChunkAsync进行分块上传,避免内存溢出。 - 添加重试机制:对HTTP请求、API调用增加重试逻辑,处理网络波动或临时错误。
- 权限校验:确保OSS存储桶权限配置正确,OAuth令牌需拥有
data:write和bucket:write权限。
内容的提问来源于stack exchange,提问作者piotkro
相关产品推荐
相关产品推荐

