生成预览链接
前提 :该文件已经同步到企业看图中
单件预览 : 直接上传单件,按接口文档中的描述传参即可(无需传递 mainFileName )
装配结构预览 : 需提前将所有装配件及主装配以压缩包(支持*.zip/*.rar)的方式上传到系统中,生成预览链接时,需指定待预览的主装配文件名 mainFileName 。
| 接口名 | POST: /login-register/open-api/documents/preview-url | |||
| 入参:(body传值) | application/json | |||
| 是否必传 | 默认值 | 描述 | 备注 | |
| fileId | 是 | - | 文件的唯一ID | 该文件在企业看图平台内的唯一ID,可在同步文件元信息接口返回值中获取 |
| fileScope | 是 | - | 文件归属域 | 我的空间->personal 项目空间(企业空间)->project |
| projectCode | 否 | - | 该项目在外部平台的唯一编码,需提前创建项目 | fileScope为project时需要传递 |
|
urlType
|
是
|
- |
链接类型
目前仅提供preview类型的链接
|
preview->标准预览链接 用途:内部查看、无权限校验 headless->无头内嵌预览,仅包含三维区域 用途:专门用于外部平台嵌入,简化头部、仅包含三维区域 |
|
mainFileName |
否 | - |
主装配全量文件名(包含后缀) |
若上传文件为压缩包格式,则需传入该参数,用于指定压缩包内待预览装配结构的主装配文件名(支持*.zip/*.rar) |
| 入参:(Header传值) | ||||
| Authorization | 是 | - | 传递AccessToken | 格式:Bearer {AccessToken} |
| 入参示例:(application/json) | ||||
|
{
"fileId": "ee1df7ccc30848589f03c2b563d721c3",
"projectCode": "proj_openapi_001",
"fileScope": "project",
"urlType": "preview",
"mainFileName": "总装.SLDASM"
}
|
||||
| 回参示例:(application/json) | ||||
|
{
"code": 0,
"msg": null,
"data": {
"previewUrl": "https://xxxxxxxx.xxx/xxx/sviewView3D",
"taskId":"aaaaaaaaaaaa" // 若此参数不为空,则说明文件需要处理,前端可将此ID作为socketId与服务端建立webSocket链接,监听处理进度,处理成功后,服务端会向前端发送成功的消息并携带 previewUrl 地址
},
"ok": true
}
|
||||
package com.hoteam.sview.demo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.core.util.JsonUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* 生成预览链接调用 Demo
* 接口:POST /login-register/open-api/documents/preview-url
* 业务成功判定:code=0 成功,code≠0 取 msg 提示
* 更新点:新增可选参数 mainFileName,压缩包装配预览时使用
*/
@Slf4j
public class PreviewUrlDemo {
// ===================== 入参实体 =====================
@Data
public static class PreviewUrlReq {
private String fileId;
private String fileScope;
private String projectCode;
private String urlType;
/**
* 【可选参数】主装配全名(带后缀)
* 若上传文件为压缩包格式(.zip/.rar),则需传入该参数,用于指定压缩包内待预览装配结构的主装配文件名
* 非压缩包单件文件预览无需传递此字段
*/
private String mainFileName;
}
// ===================== 返回体实体 =====================
@Data
public static class PreviewUrlResp {
private Integer code;
private String msg;
private Boolean ok;
private PreviewUrlData data;
}
@Data
public static class PreviewUrlData {
private String previewUrl;
private String taskId;
}
// ===================== 核心调用方法 =====================
public static PreviewUrlResp getPreviewUrl(String apiBaseUrl, String accessToken, PreviewUrlReq req) {
if (StrUtil.isBlank(apiBaseUrl)) {
log.warn("接口基础地址不能为空");
return null;
}
if (StrUtil.isBlank(accessToken)) {
log.warn("AccessToken不能为空");
return null;
}
if (req == null
|| StrUtil.isBlank(req.getFileId())
|| StrUtil.isBlank(req.getFileScope())
|| StrUtil.isBlank(req.getUrlType())) {
log.warn("预览请求必填参数缺失,req:{}", req);
return null;
}
// fileScope为project时,projectCode必填校验
if ("project".equals(req.getFileScope()) && StrUtil.isBlank(req.getProjectCode())) {
log.warn("fileScope为project时,projectCode不能为空");
return null;
}
String fullUrl = apiBaseUrl + "/login-register/open-api/documents/preview-url";
try (HttpResponse response = HttpRequest.post(fullUrl)
.header(Header.AUTHORIZATION, "Bearer " + accessToken)
.header(Header.CONTENT_TYPE, "application/json")
.body(JsonUtil.toJsonStr(req))
.timeout(60_000)
.execute()) {
int status = response.getStatus();
String respBody = response.body();
log.info("获取预览链接响应 status=[{}], body=[{}]", status, respBody);
if (status != HttpStatus.HTTP_OK) {
log.warn("获取预览链接HTTP请求失败,响应状态码=[{}]", status);
return null;
}
PreviewUrlResp resp = JsonUtil.parse(respBody, PreviewUrlResp.class);
if (resp == null) {
log.warn("预览接口返回JSON解析失败");
return null;
}
if (resp.getCode() != null && resp.getCode() != 0) {
String errMsg = StrUtil.isNotBlank(resp.getMsg()) ? resp.getMsg() : "未知业务异常";
log.warn("获取预览链接业务失败,code=[{}], msg=[{}]", resp.getCode(), errMsg);
return resp;
}
log.info("获取预览链接业务执行成功");
return resp;
} catch (Exception e) {
log.error("调用获取预览链接接口异常", e);
return null;
}
}
// ===================== Main 调用示例 =====================
public static void main(String[] args) {
String baseApi = "https://xxx.xxx.com";
String token = "你的AccessToken";
// ============示例1:单件文件预览(不需要mainFileName)============
PreviewUrlReq reqSingle = new PreviewUrlReq();
reqSingle.setFileId("平台返回的文件唯一fileId");
reqSingle.setFileScope("personal");
reqSingle.setUrlType("preview");
PreviewUrlResp resultSingle = getPreviewUrl(baseApi, token, reqSingle);
// ============示例2:压缩包装配预览(传递mainFileName)============
PreviewUrlReq reqZip = new PreviewUrlReq();
reqZip.setFileId("平台返回的压缩包文件fileId");
reqZip.setFileScope("project");
reqZip.setProjectCode("proj_openapi_001");
reqZip.setUrlType("preview");
// 压缩包内主装配文件名,必须带后缀,例如:总装.SLDASM
reqZip.setMainFileName("总装.SLDASM");
PreviewUrlResp result = getPreviewUrl(baseApi, token, reqZip);
if (result == null) {
log.error("获取预览链接请求整体失败");
return;
}
if (result.getCode() != 0) {
log.error("预览链接生成失败,错误码:{}, 错误信息:{}", result.getCode(), result.getMsg());
return;
}
PreviewUrlData data = result.getData();
if (data == null) {
log.warn("预览接口返回data节点为空");
return;
}
// 场景1:预览链接不为空,直接使用
if (StrUtil.isNotBlank(data.getPreviewUrl())) {
log.info("预览链接获取成功,预览地址:{}", data.getPreviewUrl());
return;
}
// 场景2:预览链接为空,存在taskId,需要转码监听
if (StrUtil.isNotBlank(data.getTaskId())) {
log.info("文件需要后台转码,taskId={},需建立Socket监听转码状态", data.getTaskId());
String socketUrl = baseApi + "/emh/ws/info?socketId=" + data.getTaskId();
log.info("Socket连接地址:{}", socketUrl);
return;
}
log.warn("previewUrl 和 taskId 均为空,接口返回异常");
}
}
建立socket连接
// socket地址
ws(s)://{baseURL}/emh/ws/info?socketId={taksId}
// 举例,若域名配置了oss安全证书,则需要以 wss:// 开头
// ws://10.1.201.7:9999/emh/ws/info?socketId=aaaaaaaa
// wss://www.baidu.com/emh/ws/info?socketId=aaaaaaaa
建立连接后,需要实时监听socket返回的消息,根据消息体中的status状态判断是否处理成功,根据 percent 来获取进度
消息体示例
消息体中的data参数值为String类型的字符串
{"code":0,"command":"UNCOMPRESS_FILE","data":"{\"status\":\"success\",\"percent\":\"100\",\"waitingLength\":\"0\",\"waitingTime\":\"0\"}"}
{"code":0,"command":"CONVERT_TASK","data":"{\"status\":\"processing\",\"waitingLength\":\"0\",\"waitingTime\":\"0\"}"}
{"code":0,"command":"CONVERT_TASK","data":"{\"status\":\"processing\",\"percent\":\"0\",\"waitingLength\":\"0\",\"waitingTime\":\"0\"}"}
{"code":0,"command":"CONVERT_TASK","data":"{\"status\":\"processing\",\"percent\":\"3\",\"waitingLength\":\"0\",\"waitingTime\":\"0\"}"}
{"code": 0,"command": "CONVERT_TASK","data": "{\"status\":\"success\",\"percent\":\"100\",\"waitingLength\":\"0\",\"waitingTime\":\"0\",\"previewUrl\":\"http://xxx.com/SView/sview3d.html?absoluteFileURL=t5TTERBU00uc3ZseA&fileType=svlx\"}"}
command
UNCOMPRESS_FILE : 说明当前处于文件解压阶段,后续状态为解压过程中的状态
CONVERT_TASK : 说明当前处于文件转换阶段,后续状态为转换过程中的状态
status
processing :处理中
success:成功
failed :失败
只有监听到 command 为 CONVERT_TASK ,status 为 success 时 ,才能说明文件处理成功,此时socket消息中会携带previewUrl,前端直接截取对应参数值即可完成预览连接的获取。
其他情况下,无论 command 是 UNCOMPRESS_FILE 还是 CONVERT_TASK,只要 status == failed,直接判定任务失败,结束监听
<!-- websocket客户端依赖 -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.4</version>
</dependency>
package com.hoteam.sview.demo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.JsonUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 文件转码 WebSocket 监听客户端
* 配合 PreviewUrlDemo 使用,taskId 来自预览接口返回
* 规则:
* 1、任意command下 status=failed → 整个任务失败
* 2、command=CONVERT_TASK && status=success →转码成功,取出previewUrl
*/
@Slf4j
public class PreviewWebSocketClient extends WebSocketClient {
// 用于阻塞主线程,等待转码结果返回
private final CountDownLatch latch = new CountDownLatch(1);
// 最终结果
private TranscodeResult transcodeResult;
public PreviewWebSocketClient(URI serverUri) {
super(serverUri);
}
/**
* 阻塞等待转码结果,设置最长等待超时时间
* @param timeout 超时数值
* @param unit 时间单位
* @return 转码结果
* @throws InterruptedException 中断异常
*/
public TranscodeResult waitForResult(long timeout, TimeUnit unit) throws InterruptedException {
latch.await(timeout, unit);
return transcodeResult;
}
@Override
public void onOpen(ServerHandshake handshakedata) {
log.info("WebSocket连接成功,地址:{}", getURI());
}
@Override
public void onMessage(String message) {
log.info("WebSocket收到原始消息:{}", message);
try {
// 1、解析外层消息
WsOuterMsg outerMsg = JsonUtil.parse(message, WsOuterMsg.class);
if (outerMsg == null || outerMsg.getCode() != 0) {
log.warn("websocket返回业务异常,message:{}",message);
return;
}
String command = outerMsg.getCommand();
String dataStr = outerMsg.getData();
if(StrUtil.isBlank(dataStr)){
return;
}
// 2、解析data内层JSON字符串
WsInnerMsg innerMsg = JsonUtil.parse(dataStr, WsInnerMsg.class);
if(innerMsg == null){
return;
}
String status = innerMsg.getStatus();
Integer percent = innerMsg.getPercent();
log.info("阶段:{}, 当前状态:{}, 进度:{}%",command,status,percent);
// ========== 改动:任意阶段只要status=failed,直接判定整体失败 ==========
if("failed".equals(status)){
transcodeResult = new TranscodeResult(false,null,"任务执行失败,阶段:"+ command);
log.error("任务失败,command:{}",command);
this.close();
latch.countDown();
return;
}
// ========== 成功判定:仍然必须是 CONVERT_TASK + success ==========
if("CONVERT_TASK".equals(command) && "success".equals(status)){
// 转码成功,取出预览地址
transcodeResult = new TranscodeResult(true,innerMsg.getPreviewUrl(),null);
log.info("文件转码成功!预览链接 = {}",innerMsg.getPreviewUrl());
// 关闭连接,唤醒等待线程
this.close();
latch.countDown();
}
// UNCOMPRESS_FILE 阶段的success只代表解压完成,不结束监听,继续等待后续CONVERT_TASK消息
} catch (Exception e) {
log.error("WebSocket消息解析异常,msg={}",message,e);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
log.info("WebSocket连接关闭,code={},reason={}",code,reason);
// 如果断开但是还没拿到结果,判定异常
if(transcodeResult == null){
transcodeResult = new TranscodeResult(false,null,"WebSocket提前断开连接");
latch.countDown();
}
}
@Override
public void onError(Exception ex) {
log.error("WebSocket通信异常",ex);
transcodeResult = new TranscodeResult(false,null,"WebSocket发生异常:"+ex.getMessage());
latch.countDown();
}
// ===================== 实体类 =====================
/**
* WebSocket外层消息体
*/
@Data
public static class WsOuterMsg{
private Integer code;
private String command;
/** data为JSON字符串,需要二次解析 */
private String data;
}
/**
* data内层消息体
*/
@Data
public static class WsInnerMsg{
/** processing / success / failed */
private String status;
private Integer percent;
private String waitingLength;
private String waitingTime;
/** 转码成功后返回预览url */
private String previewUrl;
}
/**
* 最终转码返回结果对象
*/
@Data
public static class TranscodeResult{
private Boolean success;
private String previewUrl;
private String errorMsg;
public TranscodeResult(Boolean success,String previewUrl,String errorMsg){
this.success = success;
this.previewUrl = previewUrl;
this.errorMsg = errorMsg;
}
}
// ===================== 调用示例 =====================
public static void main(String[] args) throws Exception {
String baseApi = "https://xxx.xxx.com";
// 从上一步预览接口拿到的taskId
String taskId = "aaaaaaaaaaaaaa";
String socketUrl = baseApi + "/emh/ws/info?socketId=" + taskId;
PreviewWebSocketClient wsClient = new PreviewWebSocketClient(new URI(socketUrl));
wsClient.connect();
// 阻塞等待转码结果,最长等待10分钟
TranscodeResult result = wsClient.waitForResult(10, TimeUnit.MINUTES);
if(result.getSuccess()){
log.info("最终预览地址:{}",result.getPreviewUrl());
}else{
log.error("转码失败:{}",result.getErrorMsg());
}
}
}