feat: 生成视频任务失败时记录并在用户前台-对话记录、管理后台-订单显示

This commit is contained in:
yys 2026-04-14 11:18:46 +08:00
parent 85dc61b07d
commit b9e08938e0
3 changed files with 140 additions and 1 deletions

View File

@ -123,7 +123,21 @@
@click="playVideo(scope.row.result)" @click="playVideo(scope.row.result)"
/> />
</template> </template>
<!-- 非链接内容 --> <!-- 视频任务失败等场景result VideoTaskError JSON -->
<template v-else-if="parseVolcTaskErrorJson(scope.row.result)">
<div class="order-result-error">
<div
v-if="parseVolcTaskErrorJson(scope.row.result).code"
class="order-result-error-line"
>
编号{{ volcFailureCodeWithHint(parseVolcTaskErrorJson(scope.row.result).code) }}
</div>
<div class="order-result-error-line">
信息{{ parseVolcTaskErrorJson(scope.row.result).message || '—' }}
</div>
</div>
</template>
<!-- 非链接内容如任务 id可点击尝试拉取视频 -->
<span v-else @click="handleOtherEvent(scope.row)">{{ scope.row.result }}</span> <span v-else @click="handleOtherEvent(scope.row)">{{ scope.row.result }}</span>
</template> </template>
</el-table-column> </el-table-column>
@ -309,6 +323,39 @@ export default {
const value = String(str || "").trim(); const value = String(str || "").trim();
return /^https?:\/\//i.test(value); return /^https?:\/\//i.test(value);
}, },
/** 已知火山错误码后附中文说明(括号) */
volcFailureCodeWithHint(code) {
const c = String(code || "").trim();
if (!c) return "";
const hints = {
OutputVideoSensitiveContentDetected: "输出视频可能包含敏感信息",
InvalidParameter: "请求参数无效"
};
const hint = hints[c];
return hint ? `${c}${hint}` : c;
},
/** result 为火山回调失败写入的 VideoTaskError JSON 时解析为 { code, message } */
parseVolcTaskErrorJson(str) {
const s = String(str || "").trim();
if (!s || s[0] !== "{") return null;
try {
const o = JSON.parse(s);
if (
o &&
typeof o === "object" &&
!Array.isArray(o) &&
("code" in o || "message" in o)
) {
return {
code: o.code != null ? String(o.code) : "",
message: o.message != null ? String(o.message) : ""
};
}
} catch (_) {
/* ignore */
}
return null;
},
// portal-ui GeneratedAssets // portal-ui GeneratedAssets
isImage(url) { isImage(url) {
const value = String(url || "").trim(); const value = String(url || "").trim();
@ -333,6 +380,7 @@ export default {
}, },
// //
async handleOtherEvent(row) { async handleOtherEvent(row) {
if (this.parseVolcTaskErrorJson(row.result)) return;
// //
if (row.isDownloading) return; if (row.isDownloading) return;
const originalResult = row.result; const originalResult = row.result;
@ -509,6 +557,19 @@ export default {
height: auto; height: auto;
} }
.order-result-error {
text-align: left;
max-width: 280px;
margin: 0 auto;
font-size: 12px;
line-height: 1.45;
color: #c45656;
}
.order-result-error-line {
word-break: break-word;
}
.text-ellipsis-two-lines { .text-ellipsis-two-lines {
/* 必须设置宽度(继承表格列宽,也可手动指定) */ /* 必须设置宽度(继承表格列宽,也可手动指定) */
width: 100%; width: 100%;

View File

@ -126,6 +126,19 @@
{{ taskStatusText(row) }} {{ taskStatusText(row) }}
</span> </span>
</div> </div>
<template v-for="(fe, feIdx) in [taskRowFailureError(row)]">
<div
v-if="fe"
:key="`${row.id || 'row'}-fe-${feIdx}`"
class="vg-chat-failure-detail">
<div v-if="fe.code" class="vg-chat-failure-line">
错误编号{{ volcFailureCodeWithHint(fe.code) }}
</div>
<div v-if="fe.message" class="vg-chat-failure-line">
信息{{ fe.message }}
</div>
</div>
</template>
</div> </div>
</div> </div>
@ -573,6 +586,51 @@ export default {
el.scrollTop = el.scrollHeight el.scrollTop = el.scrollHeight
}, },
/** 解析失败订单存库的 VideoTaskError JSON兼容旧数据、非 JSON 的 result */
parseTaskResultErrorJson(result) {
const s = String(result ?? '').trim()
if (!s || s[0] !== '{') return null
try {
const o = JSON.parse(s)
if (
o &&
typeof o === 'object' &&
!Array.isArray(o) &&
('code' in o || 'message' in o)
) {
return {
code: o.code != null ? String(o.code) : '',
message: o.message != null ? String(o.message) : ''
}
}
} catch (_) {
/* ignore */
}
return null
},
/** status=2 且 result 为错误 JSON 时返回明细,否则 null */
taskRowFailureError(row) {
const st = row?.status
if (st !== 2 && st !== '2') return null
const parsed = this.parseTaskResultErrorJson(row?.result)
if (!parsed) return null
if (!parsed.code && !parsed.message) return null
return parsed
},
/** 已知火山错误码后附中文说明(括号) */
volcFailureCodeWithHint(code) {
const c = String(code || '').trim()
if (!c) return ''
const hints = {
OutputVideoSensitiveContentDetected: '输出视频可能包含敏感信息',
InvalidParameter: '请求参数无效'
}
const hint = hints[c]
return hint ? `${c}${hint}` : c
},
taskRowResultTrim(row) { taskRowResultTrim(row) {
let r = String(row?.result ?? '').trim() let r = String(row?.result ?? '').trim()
if (r) return r if (r) return r
@ -1969,11 +2027,26 @@ export default {
.vg-chat-user-row { .vg-chat-user-row {
display: flex; display: flex;
flex-wrap: wrap;
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
} }
.vg-chat-failure-detail {
flex: 1 1 100%;
margin-top: 4px;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
font-size: 12px;
line-height: 1.45;
color: rgba(255, 180, 180, 0.92);
}
.vg-chat-failure-line {
word-break: break-word;
}
.vg-chat-user-col-main { .vg-chat-user-col-main {
flex: 1; flex: 1;
min-width: 0; min-width: 0;

View File

@ -8,8 +8,10 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.request.video.dto.VideoTaskCallBackRequest; import com.ruoyi.common.core.request.video.dto.VideoTaskCallBackRequest;
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse; import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
import com.ruoyi.common.core.response.video.dto.VideoTaskError;
import com.ruoyi.common.enums.AiOrderStatusType; import com.ruoyi.common.enums.AiOrderStatusType;
import com.ruoyi.common.enums.VideoTaskStatusType; import com.ruoyi.common.enums.VideoTaskStatusType;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.RandomStringUtil; import com.ruoyi.common.utils.RandomStringUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.TencentCosUtil; import com.ruoyi.common.utils.TencentCosUtil;
@ -398,6 +400,9 @@ public class ByteApiController extends BaseController {
return aiOrderService.volcCallbackSuccessProcess(request, taskResp, order); return aiOrderService.volcCallbackSuccessProcess(request, taskResp, order);
} else { } else {
// 前面已判断过status的合法性并处理了三种非失败的状态所以可以确定是取消失败超时 // 前面已判断过status的合法性并处理了三种非失败的状态所以可以确定是取消失败超时
if (taskResp.getError() != null) {
order.setResult(JsonUtils.toJson(taskResp.getError()));
}
aiOrderService.orderFailure(order); aiOrderService.orderFailure(order);
return AjaxResult.success(); return AjaxResult.success();
} }