Compare commits
No commits in common. "a26021dc1c55ee2e7ff7ab18becbd4ca0a8efe96" and "3328aae4e394d3ecf2c4c3e5c035af6a8c77c70f" have entirely different histories.
a26021dc1c
...
3328aae4e3
|
|
@ -74,6 +74,11 @@
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
plain
|
plain
|
||||||
|
|
@ -82,23 +87,23 @@
|
||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
v-hasPermi="['ai:order:export']"
|
v-hasPermi="['ai:order:export']"
|
||||||
>导出</el-button>
|
>导出</el-button>
|
||||||
</el-form-item>
|
</el-col>
|
||||||
</el-form>
|
|
||||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="主键ID" align="center" prop="id" width="60" />
|
<el-table-column label="主键ID" align="center" prop="id" />
|
||||||
<!-- <el-table-column label="备注" align="center" prop="remark" /> -->
|
<!-- <el-table-column label="备注" align="center" prop="remark" /> -->
|
||||||
<el-table-column label="订单编号" align="center" prop="orderNum" width="150"/>
|
<el-table-column label="订单编号" align="center" prop="orderNum" />
|
||||||
<el-table-column label="用户ID" align="center" prop="uuid" width="100" />
|
<el-table-column label="用户ID" align="center" prop="uuid" />
|
||||||
<el-table-column label="操作类型" align="center" prop="type" width="90" >
|
<el-table-column label="操作类型" align="center" prop="type">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<dict-tag :options="dict.type.ai_function_type" :value="scope.row.type" />
|
<dict-tag :options="dict.type.ai_function_type" :value="scope.row.type" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="金额" align="center" prop="amount" width="100" />
|
<el-table-column label="金额" align="center" prop="amount" />
|
||||||
<el-table-column label="生成结果" align="center" width="200">
|
<el-table-column label="生成结果" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<!-- 判断是否为链接 -->
|
<!-- 判断是否为链接 -->
|
||||||
<template v-if="isUrl(scope.row.result)">
|
<template v-if="isUrl(scope.row.result)">
|
||||||
|
|
@ -118,43 +123,7 @@
|
||||||
@click="playVideo(scope.row.result)"
|
@click="playVideo(scope.row.result)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- 视频任务失败等场景:result 为 VideoTaskError JSON(表格内简短,悬停看全文) -->
|
<!-- 非链接内容 -->
|
||||||
<template v-else-if="parseVolcTaskErrorJson(scope.row.result)">
|
|
||||||
<span
|
|
||||||
v-for="err in [parseVolcTaskErrorJson(scope.row.result)]"
|
|
||||||
:key="'oe-' + scope.row.id"
|
|
||||||
class="order-result-error-wrap"
|
|
||||||
>
|
|
||||||
<el-tooltip
|
|
||||||
placement="top-start"
|
|
||||||
effect="dark"
|
|
||||||
:open-delay="200"
|
|
||||||
popper-class="order-result-error-tooltip-popper"
|
|
||||||
>
|
|
||||||
<div slot="content" class="order-result-error-tooltip-body">
|
|
||||||
<div class="order-result-error-tooltip-row">
|
|
||||||
<span class="order-result-error-tooltip-k">错误编号:</span>
|
|
||||||
<span class="order-result-error-tooltip-v">{{ err.code || '—' }}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="volcTaskErrorHintForCode(err.code)"
|
|
||||||
class="order-result-error-tooltip-row"
|
|
||||||
>
|
|
||||||
<span class="order-result-error-tooltip-k">中文说明:</span>
|
|
||||||
<span class="order-result-error-tooltip-v">{{
|
|
||||||
volcTaskErrorHintForCode(err.code)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="order-result-error-tooltip-row order-result-error-tooltip-row--msg">
|
|
||||||
<span class="order-result-error-tooltip-k">信息:</span>
|
|
||||||
<span class="order-result-error-tooltip-v">{{ err.message || '—' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="order-result-error-compact">{{ volcTaskErrorCellSummary(err) }}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</span>
|
|
||||||
</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>
|
||||||
|
|
@ -176,7 +145,7 @@
|
||||||
>{{ scope.row.text}}</div>
|
>{{ scope.row.text}}</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="置顶" align="center" key="status" width="60">
|
<el-table-column label="置顶" align="center" key="status">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="scope.row.isTop"
|
v-model="scope.row.isTop"
|
||||||
|
|
@ -186,9 +155,9 @@
|
||||||
></el-switch>
|
></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="150"/>
|
<el-table-column label="创建时间" align="center" prop="createTime" />
|
||||||
<el-table-column label="状态" align="center" prop="status" :formatter="formatStatus" width="70"/>
|
<el-table-column label="状态" align="center" prop="status" :formatter="formatStatus" />
|
||||||
<el-table-column label="来源" align="center" prop="source" width="70"/>
|
<el-table-column label="来源" align="center" prop="source" />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<pagination
|
<pagination
|
||||||
|
|
@ -340,57 +309,6 @@ export default {
|
||||||
const value = String(str || "").trim();
|
const value = String(str || "").trim();
|
||||||
return /^https?:\/\//i.test(value);
|
return /^https?:\/\//i.test(value);
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
/** 已知错误码对应中文说明(与门户 VideoGen 一致) */
|
|
||||||
volcTaskErrorHintForCode(code) {
|
|
||||||
const c = String(code || "").trim();
|
|
||||||
const hints = {
|
|
||||||
OutputVideoSensitiveContentDetected: "输出视频可能包含敏感信息",
|
|
||||||
InvalidParameter: "请求参数无效"
|
|
||||||
};
|
|
||||||
return hints[c] || "";
|
|
||||||
},
|
|
||||||
/** 表格内摘要:已知码只显示中文,未知码显示 code */
|
|
||||||
volcTaskErrorCellSummary(err) {
|
|
||||||
if (!err) return "—";
|
|
||||||
const code = String(err.code || "").trim();
|
|
||||||
const hint = this.volcTaskErrorHintForCode(code);
|
|
||||||
if (hint) return hint;
|
|
||||||
if (code) return code;
|
|
||||||
const msg = String(err.message || "").trim();
|
|
||||||
return msg ? msg.slice(0, 32) + (msg.length > 32 ? "…" : "") : "—";
|
|
||||||
},
|
|
||||||
/** 已知火山错误码后附中文说明(括号),导出等场景可用 */
|
|
||||||
volcFailureCodeWithHint(code) {
|
|
||||||
const c = String(code || "").trim();
|
|
||||||
if (!c) return "";
|
|
||||||
const hint = this.volcTaskErrorHintForCode(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;
|
|
||||||
},
|
|
||||||
>>>>>>> seedance_score
|
|
||||||
// 判断是否为图片结果(与 portal-ui GeneratedAssets 一致)
|
// 判断是否为图片结果(与 portal-ui GeneratedAssets 一致)
|
||||||
isImage(url) {
|
isImage(url) {
|
||||||
const value = String(url || "").trim();
|
const value = String(url || "").trim();
|
||||||
|
|
@ -415,7 +333,6 @@ 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;
|
||||||
|
|
@ -592,56 +509,6 @@ export default {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-result-error-wrap {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-compact {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.35;
|
|
||||||
color: #c45656;
|
|
||||||
cursor: help;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-body {
|
|
||||||
max-width: 420px;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-row {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-row:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-row--msg .order-result-error-tooltip-v {
|
|
||||||
display: block;
|
|
||||||
margin-top: 2px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-k {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fde2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-result-error-tooltip-v {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-ellipsis-two-lines {
|
.text-ellipsis-two-lines {
|
||||||
/* 必须设置宽度(继承表格列宽,也可手动指定) */
|
/* 必须设置宽度(继承表格列宽,也可手动指定) */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,6 @@ export default {
|
||||||
})
|
})
|
||||||
this.username = ""
|
this.username = ""
|
||||||
this.password = ""
|
this.password = ""
|
||||||
this.$router.replace({ name: 'video-gen' })
|
|
||||||
this.$emit('cancel')
|
this.$emit('cancel')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ import { constantRoutes } from '@/router/index.js'
|
||||||
import Login from './Login.vue'
|
import Login from './Login.vue'
|
||||||
import { LOCALE_NAMES } from '@/lang/i18n'
|
import { LOCALE_NAMES } from '@/lang/i18n'
|
||||||
import User from './User.vue'
|
import User from './User.vue'
|
||||||
import { getToken } from '@/utils/auth'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'nav-bar',
|
name: 'nav-bar',
|
||||||
|
|
@ -183,9 +182,6 @@ export default {
|
||||||
this.openLogin()
|
this.openLogin()
|
||||||
}
|
}
|
||||||
this.getLogo()
|
this.getLogo()
|
||||||
if (getToken()) {
|
|
||||||
this.$store.dispatch('user/getInfo').catch(() => {})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openLogin() {
|
openLogin() {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const constantRoutes = [{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: {
|
redirect: {
|
||||||
name: 'video-gen'
|
name: 'index'
|
||||||
},
|
},
|
||||||
children: [{
|
children: [{
|
||||||
path: 'index',
|
path: 'index',
|
||||||
|
|
|
||||||
|
|
@ -149,20 +149,14 @@
|
||||||
<span class="vg-chat-inline-status" :class="chatRowInlineStatusClass(row)">
|
<span class="vg-chat-inline-status" :class="chatRowInlineStatusClass(row)">
|
||||||
{{ taskStatusText(row) }}
|
{{ taskStatusText(row) }}
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="vg-link vg-chat-inline-cancel"
|
||||||
|
v-if="row.result && row.status === 0"
|
||||||
|
@click="cancelRowTask(row)">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
</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>
|
||||||
|
|
||||||
|
|
@ -610,51 +604,6 @@ 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
|
||||||
|
|
@ -1233,7 +1182,6 @@ export default {
|
||||||
this.videoId = res.data.id
|
this.videoId = res.data.id
|
||||||
this.showResult = true
|
this.showResult = true
|
||||||
this.$refs.videoComposeRef?.clearPromptOnly?.()
|
this.$refs.videoComposeRef?.clearPromptOnly?.()
|
||||||
this.$store.dispatch('user/getInfo').catch(() => {})
|
|
||||||
this.getVideo(res.data.id)
|
this.getVideo(res.data.id)
|
||||||
this.refreshChatFirstPage()
|
this.refreshChatFirstPage()
|
||||||
} else if (res.code == -1) {
|
} else if (res.code == -1) {
|
||||||
|
|
@ -1285,7 +1233,6 @@ export default {
|
||||||
this.$message.warning('任务已完成但未返回视频地址,请稍后重试或联系管理员')
|
this.$message.warning('任务已完成但未返回视频地址,请稍后重试或联系管理员')
|
||||||
}
|
}
|
||||||
this.destroyInterval()
|
this.destroyInterval()
|
||||||
this.$store.dispatch('user/getInfo').catch(() => {})
|
|
||||||
this.refreshChatFirstPage()
|
this.refreshChatFirstPage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -2083,26 +2030,11 @@ 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;
|
||||||
|
|
@ -2138,6 +2070,11 @@ export default {
|
||||||
color: rgba(255, 107, 107, 0.95);
|
color: rgba(255, 107, 107, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vg-chat-inline-cancel {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.vg-chat-params {
|
.vg-chat-params {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,29 @@
|
||||||
package com.ruoyi.api;
|
package com.ruoyi.api;
|
||||||
|
|
||||||
import com.ruoyi.ai.domain.*;
|
import com.ruoyi.ai.domain.*;
|
||||||
import com.ruoyi.ai.service.*;
|
import com.ruoyi.ai.service.IAiManagerService;
|
||||||
|
import com.ruoyi.ai.service.IAiOrderService;
|
||||||
|
import com.ruoyi.ai.service.IAiTagService;
|
||||||
|
import com.ruoyi.ai.service.IByteService;
|
||||||
import com.ruoyi.api.request.ByteApiRequest;
|
import com.ruoyi.api.request.ByteApiRequest;
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
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.domain.model.LoginAiUser;
|
||||||
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.VideoTaskStatusType;
|
|
||||||
import com.ruoyi.common.utils.JsonUtils;
|
|
||||||
import com.ruoyi.common.utils.RandomStringUtil;
|
import com.ruoyi.common.utils.RandomStringUtil;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.TencentCosUtil;
|
import com.ruoyi.common.utils.TencentCosUtil;
|
||||||
import com.ruoyi.common.utils.ip.IpUtils;
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.redisson.api.RLock;
|
|
||||||
import org.redisson.api.RedissonClient;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,22 +34,20 @@ import java.util.regex.Pattern;
|
||||||
@Api(tags = "生成内容")
|
@Api(tags = "生成内容")
|
||||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||||
public class ByteApiController extends BaseController {
|
public class ByteApiController extends BaseController {
|
||||||
// 回调时分布式锁的key前缀
|
|
||||||
private static final String VOLC_CALLBACK_LOCK_KEY_PREFIX = "volc:callback:lock:";
|
|
||||||
// 锁参数
|
|
||||||
private static final int LOCK_WAIT_SECONDS = 10;
|
|
||||||
private static final int LOCK_LEASE_SECONDS = 20;
|
|
||||||
|
|
||||||
private final IByteService byteService;
|
private final IByteService byteService;
|
||||||
private final TencentCosUtil tencentCosUtil;
|
private final TencentCosUtil tencentCosUtil;
|
||||||
private final IAiOrderService aiOrderService;
|
private final IAiOrderService aiOrderService;
|
||||||
private final IAiManagerService managerService;
|
private final IAiManagerService managerService;
|
||||||
private final IAiTagService aiTagService;
|
private final IAiTagService aiTagService;
|
||||||
private final RedissonClient redissonClient;
|
@Value("${byteapi.callBackUrl}")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
// 火山引擎配置
|
||||||
|
@Value("${volcengine.ark.baseUrl}")
|
||||||
|
private String volcBaseUrl;
|
||||||
@Value("${volcengine.ark.callbackUrl}")
|
@Value("${volcengine.ark.callbackUrl}")
|
||||||
private String volcCallbackUrl;
|
private String volcCallbackUrl;
|
||||||
private final IByteDeptApiKeyService byteDeptApiKeyService;
|
|
||||||
|
|
||||||
@PostMapping("/promptToImg")
|
@PostMapping("/promptToImg")
|
||||||
@ApiOperation("文生图")
|
@ApiOperation("文生图")
|
||||||
|
|
@ -340,209 +331,128 @@ public class ByteApiController extends BaseController {
|
||||||
@PostMapping(value = "/volcCallback")
|
@PostMapping(value = "/volcCallback")
|
||||||
@ApiOperation("火山引擎视频回调")
|
@ApiOperation("火山引擎视频回调")
|
||||||
@Anonymous
|
@Anonymous
|
||||||
public AjaxResult volcCallback(@RequestBody VideoTaskCallBackRequest request, HttpServletRequest httpRequest) throws Exception {
|
public AjaxResult volcCallback(@RequestBody ByteBodyRes byteBodyRes) throws Exception {
|
||||||
logger.info("volcCallback 收到回调数据: clientIp = {}, host = {}, request = {}", IpUtils.getIpAddr(httpRequest),
|
logger.info("volcCallback 收到回调数据: {}", byteBodyRes);
|
||||||
httpRequest.getHeader("Host"), request);
|
String id = byteBodyRes.getId();
|
||||||
// 1、基础参数校验
|
if (StringUtils.isEmpty(id)) {
|
||||||
AjaxResult result = volcCallbackBaseCheck(request);
|
logger.warn("volcCallback 无任务 id,跳过业务处理");
|
||||||
if (result != null) {
|
return AjaxResult.success("callback success");
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 2、查询订单
|
|
||||||
String taskId = request.getId();
|
|
||||||
AiOrder order = aiOrderService.selectOneByThirdPartyOrderNum(taskId);
|
|
||||||
if (order == null) {
|
|
||||||
// 可能是其他环境生成的但回调地址配置成正式的
|
|
||||||
logger.warn("volcCallback aiorder is not exist! third party order num = {}", taskId);
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
// 3、从官方获取任务数据
|
|
||||||
// 根据订单用户ID查询使用的Key
|
|
||||||
// 严格来讲,按逻辑这块是应放在锁内,但这是调外部接口,如果接口超时整个服务可能会当机,所以不放锁内,即不做强一致
|
|
||||||
String apiKey = byteDeptApiKeyService.resolveVolcApiKey(order.getUserId());
|
|
||||||
GetVideoGenerationTaskResponse taskResp = byteService.getVideoGenerationTasks(request.getId(), apiKey);
|
|
||||||
// 4、官方数据校验
|
|
||||||
result = volcCallbackByteCheck(request, taskResp);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 5、查询订单(同 taskId 串行:Redisson 分布式锁;步骤 1~3 已在锁外)
|
|
||||||
String lockKey = VOLC_CALLBACK_LOCK_KEY_PREFIX + taskId;
|
|
||||||
RLock lock = redissonClient.getLock(lockKey);
|
|
||||||
boolean locked = false;
|
|
||||||
try {
|
|
||||||
locked = lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
|
|
||||||
if (!locked) {
|
|
||||||
logger.warn("volcCallback skip: concurrent handling for same task, third party order num = {}", taskId);
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
// 锁内二次查询,防止并发时状态变更
|
|
||||||
order = aiOrderService.selectOneByThirdPartyOrderNum(taskId);
|
|
||||||
if (order == null) {
|
|
||||||
// 可能是其他环境生成的但回调地址配置成正式的
|
|
||||||
logger.warn("volcCallback aiorder is not exist! third party order num = {}", taskId);
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
// 6、状态为队列中、执行中,只更新任务状态
|
|
||||||
result = volcCallbackRunningTaskProcess(taskResp, order);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 7、订单数据校验
|
|
||||||
result = volcCallbackOrderCheck(taskResp, order);
|
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 8、根据状态做不同的处理(加事务)
|
|
||||||
String status = taskResp.getStatus().toLowerCase();
|
|
||||||
if (VideoTaskStatusType.SUCCEEDED.getName().equals(status)) {
|
|
||||||
// 成功,预扣
|
|
||||||
return aiOrderService.volcCallbackSuccessProcess(request, taskResp, order);
|
|
||||||
} else {
|
|
||||||
// 前面已判断过status的合法性,并处理了三种非失败的状态,所以可以确定是取消、失败、超时
|
|
||||||
if (taskResp.getError() != null) {
|
|
||||||
order.setResult(JsonUtils.toJson(taskResp.getError()));
|
|
||||||
}
|
|
||||||
aiOrderService.orderFailure(order);
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
logger.error("volcCallback interrupted while waiting for lock, third party order num = {}", taskId, e);
|
|
||||||
return AjaxResult.error();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("volcCallback error! third party order num = {}, status = {}",
|
|
||||||
request.getId(), request.getStatus(), ex);
|
|
||||||
return AjaxResult.error();
|
|
||||||
} finally {
|
|
||||||
if (locked && lock.isHeldByCurrentThread()) {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AjaxResult volcCallbackOrderCheck(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
Integer code = byteBodyRes.getCode();
|
||||||
// 订单状态如果不为执行中则不做处理
|
boolean codeError = code != null && code != 200;
|
||||||
if (order.getStatus() != null && order.getStatus() != AiOrderStatusType.RUNNING.ordinal()) {
|
String st = byteBodyRes.getStatus();
|
||||||
logger.warn("volcCallback aiorder's status is not running! third party order num = {}, order status = {}"
|
|
||||||
, taskResp.getId(), order.getStatus());
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
if (order.getIsBackfilled() != null && order.getIsBackfilled() == 1) {
|
|
||||||
// 已回补过,不再回补,直接返回成功
|
|
||||||
logger.warn("volcCallback is back filled! third party order num = {}, order status = {}"
|
|
||||||
, taskResp.getId(), order.getStatus());
|
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AjaxResult volcCallbackByteCheck(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp) {
|
if (st != null && !st.isEmpty()) {
|
||||||
String requestStatus = request.getStatus().toLowerCase();
|
// 执行中状态
|
||||||
String responseStatus = taskResp.getStatus().toLowerCase();
|
|
||||||
// 请求的状态与字节的状态是否一致
|
|
||||||
if (!requestStatus.equals(responseStatus)) {
|
|
||||||
// 如果推送的是队列中、执行中,但官方任务可能已进到下一步的状态,(时间差)此种情况不处理,等待后续推送
|
|
||||||
if (requestStatus.equals(VideoTaskStatusType.QUEUED.getName())
|
|
||||||
|| requestStatus.equals(VideoTaskStatusType.RUNNING.getName())) {
|
|
||||||
logger.warn("volcCallback request's status != official status, no process! order third party order num = {}, request's status = {}, official status = {}",
|
|
||||||
request.getId(), requestStatus, responseStatus);
|
|
||||||
// 防止再次推送
|
|
||||||
return AjaxResult.success();
|
|
||||||
} else {
|
|
||||||
logger.error("volcCallback request's status != official status! order third party order num = {}, request's status = {}, official status = {}",
|
|
||||||
request.getId(), requestStatus, responseStatus);
|
|
||||||
// 不再让对方二次推送
|
|
||||||
return AjaxResult.error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AjaxResult volcCallbackBaseCheck(VideoTaskCallBackRequest request) {
|
|
||||||
// 参数校验
|
|
||||||
String status = request.getStatus();
|
|
||||||
if (StringUtils.isEmpty(request.getId()) || StringUtils.isEmpty(status)) {
|
|
||||||
logger.error("volcCallbackBaseCheck id or status is null! third party order num = {}, status = {}"
|
|
||||||
, request.getId(), status);
|
|
||||||
return AjaxResult.error("id or status is null!");
|
|
||||||
}
|
|
||||||
// 状态是否正确
|
|
||||||
if (!VideoTaskStatusType.isValidName(status)) {
|
|
||||||
logger.error("volcCallbackBaseCheck invalid status! third party order num = {}, status = {}"
|
|
||||||
, request.getId(), status);
|
|
||||||
return AjaxResult.error();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AjaxResult volcCallbackRunningTaskProcess(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
|
||||||
// 执行中状态 ,更新到ext_status字段
|
|
||||||
Integer extStatus = null;
|
Integer extStatus = null;
|
||||||
String status = taskResp.getStatus().toLowerCase();
|
if ("queued".equals(st)) {
|
||||||
if (VideoTaskStatusType.QUEUED.getName().equals(status)) {
|
|
||||||
extStatus = 0;
|
extStatus = 0;
|
||||||
} else if (VideoTaskStatusType.RUNNING.getName().equals(status)) {
|
} else if ("running".equals(st)) {
|
||||||
extStatus = 1;
|
extStatus = 1;
|
||||||
}
|
}
|
||||||
if (extStatus != null) {
|
if (extStatus != null) {
|
||||||
order.setExtStatus(extStatus);
|
AiOrder order = findAiOrderByVolcTaskId(id);
|
||||||
aiOrderService.updateAiOrder(order);
|
if (order == null) {
|
||||||
logger.info("volcCallback order extStatus is updated! third party order num = {}, extStatus = {}"
|
logger.warn("volcCallback 修改执行中状态,未找到任务对应订单, id={}, {}", id, st);
|
||||||
, taskResp.getId(), order.getExtStatus());
|
return AjaxResult.success("callback success");
|
||||||
return AjaxResult.success();
|
|
||||||
}
|
}
|
||||||
return null;
|
// if (order.getStatus() != 0) {
|
||||||
|
// logger.warn("订单状态不为0, 因此不修改ext_status, id = {}, status = {}, order status = {}", id, st, order.getStatus());
|
||||||
|
// return AjaxResult.success("callback success");
|
||||||
|
// }
|
||||||
|
AiOrder upd = new AiOrder();
|
||||||
|
upd.setId(order.getId());
|
||||||
|
upd.setExtStatus(extStatus);
|
||||||
|
aiOrderService.updateAiOrder(upd);
|
||||||
|
return AjaxResult.success("callback success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeError) {
|
||||||
|
markVolcCallbackOrderClearResultFailed(id, "code=" + code);
|
||||||
|
return AjaxResult.success("callback success");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("succeeded".equals(st)) {
|
||||||
|
content contentObj = byteBodyRes.getContent();
|
||||||
|
if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
|
||||||
|
String videoUrl = contentObj.getVideo_url();
|
||||||
|
videoUrl = tencentCosUtil.uploadFileByUrl(videoUrl);
|
||||||
|
contentObj.setVideo_url(videoUrl);
|
||||||
|
|
||||||
|
AiOrder aiOrderByResult = findAiOrderByVolcTaskId(id);
|
||||||
|
if (aiOrderByResult != null) {
|
||||||
|
AiOrder aiOrder = new AiOrder();
|
||||||
|
aiOrder.setId(aiOrderByResult.getId());
|
||||||
|
aiOrder.setResult(videoUrl);
|
||||||
|
aiOrder.setStatus(1);
|
||||||
|
aiOrderService.updateAiOrder(aiOrder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleVolcCallbackFailure(id, "succeeded但缺少video_url");
|
||||||
|
}
|
||||||
|
return AjaxResult.success("callback success");
|
||||||
|
}
|
||||||
|
|
||||||
|
// failed、canceled 等终态,或 status 与成功/进行中均不一致
|
||||||
|
if (StringUtils.isNotEmpty(st) || code != null) {
|
||||||
|
handleVolcCallbackFailure(id, "status=" + st);
|
||||||
|
} else {
|
||||||
|
logger.warn("volcCallback 未携带可判定的 status/code,id={}", id);
|
||||||
|
}
|
||||||
|
return AjaxResult.success("callback success");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调体 code 非 200(HTTP/业务状态码):清空 result,status=2,不退款。
|
* 回调体 code 非 200(HTTP/业务状态码):清空 result,status=2,不退款。
|
||||||
*/
|
*/
|
||||||
// private void markVolcCallbackOrderClearResultFailed(String taskId, String reason) {
|
private void markVolcCallbackOrderClearResultFailed(String taskId, String reason) {
|
||||||
// AiOrder order = findAiOrderByVolcTaskId(taskId);
|
AiOrder order = findAiOrderByVolcTaskId(taskId);
|
||||||
// if (order == null) {
|
if (order == null) {
|
||||||
// logger.warn("volcCallback code 非 200:未找到任务对应订单, taskId={}, {}", taskId, reason);
|
logger.warn("volcCallback code 非 200:未找到任务对应订单, taskId={}, {}", taskId, reason);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// AiOrder upd = new AiOrder();
|
AiOrder upd = new AiOrder();
|
||||||
// upd.setId(order.getId());
|
upd.setId(order.getId());
|
||||||
// upd.setResult("");
|
upd.setResult("");
|
||||||
// upd.setStatus(2);
|
upd.setStatus(2);
|
||||||
// aiOrderService.updateAiOrder(upd);
|
aiOrderService.updateAiOrder(upd);
|
||||||
// logger.warn("volcCallback code 非 200,已清空 result 并 status=2, orderId={}, {}", order.getId(), reason);
|
logger.warn("volcCallback code 非 200,已清空 result 并 status=2, orderId={}, {}", order.getId(), reason);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// private AiOrder findAiOrderByVolcTaskId(String taskId) {
|
private AiOrder findAiOrderByVolcTaskId(String taskId) {
|
||||||
// AiOrder order = aiOrderService.getAiOrderByPortalVideoTask(taskId);
|
AiOrder order = aiOrderService.getAiOrderByPortalVideoTask(taskId);
|
||||||
// if (order != null) {
|
if (order != null) {
|
||||||
// return order;
|
return order;
|
||||||
// }
|
}
|
||||||
// return aiOrderService.getAiOrderByResult(taskId);
|
return aiOrderService.getAiOrderByResult(taskId);
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调判定任务失败:订单仍持有火山任务 id、且非已失败时,标记失败并退余额(orderFailure)。
|
* 回调判定任务失败:订单仍持有火山任务 id、且非已失败时,标记失败并退余额(orderFailure)。
|
||||||
*/
|
*/
|
||||||
// private void handleVolcCallbackFailure(String taskId, String reason) {
|
private void handleVolcCallbackFailure(String taskId, String reason) {
|
||||||
// AiOrder order = aiOrderService.getAiOrderByResult(taskId);
|
AiOrder order = aiOrderService.getAiOrderByResult(taskId);
|
||||||
// if (order == null) {
|
if (order == null) {
|
||||||
// logger.warn("volcCallback 失败:未找到 result={} 的订单, {}", taskId, reason);
|
logger.warn("volcCallback 失败:未找到 result={} 的订单, {}", taskId, reason);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// if (!taskId.equals(order.getResult())) {
|
if (!taskId.equals(order.getResult())) {
|
||||||
// logger.info("volcCallback 失败处理跳过:订单结果已更新, taskId={}, {}", taskId, reason);
|
logger.info("volcCallback 失败处理跳过:订单结果已更新, taskId={}, {}", taskId, reason);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// if (Integer.valueOf(2).equals(order.getStatus())) {
|
if (Integer.valueOf(2).equals(order.getStatus())) {
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// aiOrderService.orderFailure(order);
|
aiOrderService.orderFailure(order);
|
||||||
// logger.warn("volcCallback 任务失败,已标记订单失败并退款, taskId={}, reason={}", taskId, reason);
|
logger.warn("volcCallback 任务失败,已标记订单失败并退款, taskId={}, reason={}", taskId, reason);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// @PostMapping(value = "/{id}/cancel")
|
@PostMapping(value = "/{id}/cancel")
|
||||||
// @ApiOperation("取消视频生成任务")
|
@ApiOperation("取消视频生成任务")
|
||||||
// public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
|
public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
|
||||||
// return byteService.cancelVideoTask(id);
|
return byteService.cancelVideoTask(id);
|
||||||
// }
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.ruoyi.api;
|
package com.ruoyi.api;
|
||||||
|
|
||||||
import cn.hutool.core.util.NumberUtil;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
@ -13,12 +12,10 @@ import com.ruoyi.ai.domain.ContentItem;
|
||||||
import com.ruoyi.ai.domain.ImageUrl;
|
import com.ruoyi.ai.domain.ImageUrl;
|
||||||
import com.ruoyi.ai.domain.content;
|
import com.ruoyi.ai.domain.content;
|
||||||
import com.ruoyi.ai.service.IAiOrderService;
|
import com.ruoyi.ai.service.IAiOrderService;
|
||||||
import com.ruoyi.ai.service.IAiUserService;
|
|
||||||
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
||||||
import com.ruoyi.ai.service.IByteService;
|
import com.ruoyi.ai.service.IByteService;
|
||||||
import com.ruoyi.api.request.PortalVideoGenRequest;
|
import com.ruoyi.api.request.PortalVideoGenRequest;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
|
@ -33,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -55,7 +51,6 @@ public class PortalVideoController extends BaseController {
|
||||||
private final IAiOrderService aiOrderService;
|
private final IAiOrderService aiOrderService;
|
||||||
private final TencentCosUtil tencentCosUtil;
|
private final TencentCosUtil tencentCosUtil;
|
||||||
private final PortalVideoProperties portalVideoProperties;
|
private final PortalVideoProperties portalVideoProperties;
|
||||||
private final IAiUserService aiUserService;
|
|
||||||
|
|
||||||
@Value("${volcengine.ark.callbackUrl:}")
|
@Value("${volcengine.ark.callbackUrl:}")
|
||||||
private String volcCallbackUrl;
|
private String volcCallbackUrl;
|
||||||
|
|
@ -192,9 +187,7 @@ public class PortalVideoController extends BaseController {
|
||||||
private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body, String functionTypeResolved) {
|
private void fillVideoOrderRecord(AiOrder aiOrder, PortalVideoGenRequest req, String mode, ByteBodyReq body, String functionTypeResolved) {
|
||||||
aiOrder.setText(req.getText());
|
aiOrder.setText(req.getText());
|
||||||
aiOrder.setMode(mode);
|
aiOrder.setMode(mode);
|
||||||
aiOrder.setIsBackfilled(0);
|
|
||||||
applyOrderImages(aiOrder, req);
|
applyOrderImages(aiOrder, req);
|
||||||
aiOrder.setExtStatus(0);
|
|
||||||
if (req.getDuration() != null) {
|
if (req.getDuration() != null) {
|
||||||
aiOrder.setDuration(req.getDuration());
|
aiOrder.setDuration(req.getDuration());
|
||||||
} else if (body.getDuration() != null) {
|
} else if (body.getDuration() != null) {
|
||||||
|
|
@ -263,7 +256,6 @@ public class PortalVideoController extends BaseController {
|
||||||
|
|
||||||
private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) {
|
private AjaxResult submitOrderAndCreate(PortalVideoGenRequest req, String mode, ByteBodyReq byteBodyReq) {
|
||||||
String functionType = resolveFunctionType(req);
|
String functionType = resolveFunctionType(req);
|
||||||
// 判断余额是否足够,aimanager里配置了最低限额。创建订单
|
|
||||||
AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
|
AiOrder aiOrder = aiOrderService.getAiOrder(functionType);
|
||||||
if (aiOrder == null) {
|
if (aiOrder == null) {
|
||||||
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
return AjaxResult.error(-1, "You have a low balance, please recharge");
|
||||||
|
|
@ -274,45 +266,14 @@ public class PortalVideoController extends BaseController {
|
||||||
|
|
||||||
String key = apiKey();
|
String key = apiKey();
|
||||||
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
|
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
|
||||||
String thirdPartyOrderNumId = byteBodyRes.getId();
|
String id = byteBodyRes.getId();
|
||||||
if (thirdPartyOrderNumId == null) {
|
if (id == null) {
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
return AjaxResult.error(-2, "generation failed, balance has been refunded");
|
||||||
}
|
}
|
||||||
mergeVolcTaskIdIntoVideoParams(aiOrder, thirdPartyOrderNumId);
|
mergeVolcTaskIdIntoVideoParams(aiOrder, id);
|
||||||
aiOrder.setResult(thirdPartyOrderNumId);
|
aiOrder.setResult(id);
|
||||||
// 字节订单号与请求ID
|
aiOrderService.orderSuccess(aiOrder);
|
||||||
aiOrder.setThirdPartyOrderNum(thirdPartyOrderNumId);
|
|
||||||
aiOrder.setVideoGenRequestId(byteBodyRes.getRequestId());
|
|
||||||
// aiOrderService.orderSuccess(aiOrder);
|
|
||||||
aiOrderService.updateAiOrder(aiOrder);
|
|
||||||
|
|
||||||
// !!!!! 逻辑暂时停用,先不预扣 start !!!!!
|
|
||||||
// 查询任务详情,按字节返回的预扣数量扣减
|
|
||||||
// GetVideoGenerationTaskResponse task = byteService.getVideoGenerationTasks(thirdPartyOrderNumId, key);
|
|
||||||
// if (task == null || task.getUsage() == null || task.getUsage().getTotalTokens() == null) {
|
|
||||||
// return AjaxResult.error(-2, "generation failed, byte task's usage is null");
|
|
||||||
// }
|
|
||||||
// if (task.getUsage().getTotalTokens() <= 0) {
|
|
||||||
// return AjaxResult.error(-2, "generation failed, byte task's totalTokens <= 0");
|
|
||||||
// }
|
|
||||||
// BigDecimal totalTokens = new BigDecimal(task.getUsage().getTotalTokens());
|
|
||||||
// // 同步设置aiOrder,以防在抛异常时数值没变
|
|
||||||
// aiOrder.setPreDeductAmount(totalTokens);
|
|
||||||
// aiOrder.setAmount(totalTokens);
|
|
||||||
|
|
||||||
// 设置订单信息
|
|
||||||
// AiOrder updAiOrder = new AiOrder();
|
|
||||||
// updAiOrder.setId(aiOrder.getId());
|
|
||||||
// updAiOrder.setOrderNum(aiOrder.getOrderNum());
|
|
||||||
// updAiOrder.setPreDeductAmount(totalTokens);
|
|
||||||
// 先设置成预扣数量,等收到回调再改过来,这样后续报表会比较准确
|
|
||||||
// updAiOrder.setAmount(totalTokens);
|
|
||||||
// aiOrderService.updateAiOrder(updAiOrder);
|
|
||||||
// 扣减余额
|
|
||||||
// aiUserService.addUserBalance(aiOrder.getOrderNum(), SecurityUtils.getAiUserId()
|
|
||||||
// , NumberUtil.mul(-1, totalTokens), aiOrderService.getChangerType(functionType));
|
|
||||||
// !!!!! 逻辑暂时停用,先不预扣 start !!!!!
|
|
||||||
return AjaxResult.success(byteBodyRes);
|
return AjaxResult.success(byteBodyRes);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
|
|
@ -637,19 +598,19 @@ public class PortalVideoController extends BaseController {
|
||||||
return AjaxResult.success(byteBodyRes);
|
return AjaxResult.success(byteBodyRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @DeleteMapping("/tasks/{taskId}")
|
@DeleteMapping("/tasks/{taskId}")
|
||||||
// @ApiOperation("删除或取消视频生成任务")
|
@ApiOperation("删除或取消视频生成任务")
|
||||||
// public AjaxResult deleteOrCancelTask(@PathVariable String taskId) throws Exception {
|
public AjaxResult deleteOrCancelTask(@PathVariable String taskId) throws Exception {
|
||||||
// Long uid = SecurityUtils.getAiUserId();
|
Long uid = SecurityUtils.getAiUserId();
|
||||||
// AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
|
AiOrder owned = aiOrderService.getAiOrderByPortalVideoTask(taskId);
|
||||||
// if (owned == null || !uid.equals(owned.getUserId())) {
|
if (owned == null || !uid.equals(owned.getUserId())) {
|
||||||
// return AjaxResult.error("无权操作该任务");
|
return AjaxResult.error("无权操作该任务");
|
||||||
// }
|
}
|
||||||
// String key = apiKey();
|
String key = apiKey();
|
||||||
// AjaxResult cancelRes = byteService.cancelVideoTask(taskId, key);
|
AjaxResult cancelRes = byteService.cancelVideoTask(taskId, key);
|
||||||
// if (cancelRes.isSuccess() && owned.getStatus() != null && owned.getStatus() == 0) {
|
if (cancelRes.isSuccess() && owned.getStatus() != null && owned.getStatus() == 0) {
|
||||||
// aiOrderService.orderFailure(owned);
|
aiOrderService.orderFailure(owned);
|
||||||
// }
|
}
|
||||||
// return cancelRes;
|
return cancelRes;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,9 @@ volcengine:
|
||||||
ak: AKLTNmYyN2VhZTcyMDcxNDNlNzg3OGVlMDVmZjRhNWQwY2M
|
ak: AKLTNmYyN2VhZTcyMDcxNDNlNzg3OGVlMDVmZjRhNWQwY2M
|
||||||
sk: Tm1ZeU1UTmlORFk1WmpKa05HUmpaRGcxTWpjMFpqUmpOVE01TUdJME5URQ==
|
sk: Tm1ZeU1UTmlORFk1WmpKa05HUmpaRGcxTWpjMFpqUmpOVE01TUdJME5URQ==
|
||||||
projectAesKeyBase64: "gJajABVfQJ9xA94Q9IvQi68fqqhSIkfcKlG7pjGFt2U="
|
projectAesKeyBase64: "gJajABVfQJ9xA94Q9IvQi68fqqhSIkfcKlG7pjGFt2U="
|
||||||
|
url: https://ark.ap-southeast.bytepluses.com/api/v3
|
||||||
|
apiKey: 3e33e034-7e25-4228-8864-b51b2a7a8f97
|
||||||
|
callBackUrl: http://47.86.170.114:5173/
|
||||||
|
|
||||||
# 门户视频生成页:模型 / 比例 / 时长 / 分辨率均由此处维护,前后端不写死业务枚举
|
# 门户视频生成页:模型 / 比例 / 时长 / 分辨率均由此处维护,前后端不写死业务枚举
|
||||||
portal:
|
portal:
|
||||||
|
|
@ -278,8 +281,8 @@ portal:
|
||||||
- 14
|
- 14
|
||||||
- 15
|
- 15
|
||||||
resolutions:
|
resolutions:
|
||||||
- "480p"
|
|
||||||
- "720p"
|
- "720p"
|
||||||
|
- "1080p"
|
||||||
|
|
||||||
jinsha:
|
jinsha:
|
||||||
url: https://api.jinshapay.xyz
|
url: https://api.jinshapay.xyz
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,3 @@ no.update.permission=您没有修改数据的权限,请联系管理员添加
|
||||||
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
|
no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
|
||||||
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
|
no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
|
||||||
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
|
no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
|
||||||
|
|
||||||
# video generation
|
|
||||||
order.number.generation.failed=订单号 {0} 生成失败,请稍后重试。
|
|
||||||
order.number.generation.submit=订单号 {0} 生成任务已提交!
|
|
||||||
order.number.generation.successbackfill=订单号 {0} 生成成功!金额已回补!
|
|
||||||
|
|
@ -30,8 +30,4 @@ email.verification.code.error=Verification code is incorrect, please try again.
|
||||||
# User not found
|
# User not found
|
||||||
user.not.found=User not found.
|
user.not.found=User not found.
|
||||||
user.password.incorrect=Password is incorrect, please try again.
|
user.password.incorrect=Password is incorrect, please try again.
|
||||||
|
|
||||||
# video generation
|
|
||||||
order.number.generation.failed=Order number {0} generation failed, please try again later.
|
order.number.generation.failed=Order number {0} generation failed, please try again later.
|
||||||
order.number.generation.submit=Order number {0} generation task submitted!
|
|
||||||
order.number.generation.successbackfill=Order number {0} sucessed! Amount is back filled!
|
|
||||||
|
|
@ -29,7 +29,4 @@ email.verification.code.error=驗證碼錯誤,請重新輸入。
|
||||||
user.not.found=用戶不存在。
|
user.not.found=用戶不存在。
|
||||||
user.password.incorrect=密碼錯誤,請重新輸入。
|
user.password.incorrect=密碼錯誤,請重新輸入。
|
||||||
|
|
||||||
# video generation
|
|
||||||
order.number.generation.failed=訂單號 {0} 生成失敗,請稍後重試。
|
order.number.generation.failed=訂單號 {0} 生成失敗,請稍後重試。
|
||||||
order.number.generation.submit=訂單號 {0} 生成任務已提交!
|
|
||||||
order.number.generation.successbackfill=訂單號 {0} 生成成功!金額已回補!
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package com.ruoyi.common.core.request.video.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoGenerationUsage;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskContent;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskError;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskTool;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "生成视频任务的回调接口")
|
|
||||||
public class VideoTaskCallBackRequest {
|
|
||||||
@ApiModelProperty(value = "视频生成任务 ID。")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "任务使用的模型名称和版本,模型名称-版本。")
|
|
||||||
private String model;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "任务状态:queued 排队中;running 运行中;cancelled 已取消;succeeded 成功;failed 失败;expired 超时。")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "错误提示信息,任务成功时为 null。")
|
|
||||||
private VideoTaskError error;
|
|
||||||
|
|
||||||
@JsonProperty("created_at")
|
|
||||||
@ApiModelProperty(value = "任务创建时间的 Unix 时间戳(秒)。")
|
|
||||||
private Integer createdAt;
|
|
||||||
|
|
||||||
@JsonProperty("updated_at")
|
|
||||||
@ApiModelProperty(value = "任务当前状态更新时间的 Unix 时间戳(秒)。")
|
|
||||||
private Integer updatedAt;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "视频生成任务的输出内容。")
|
|
||||||
private VideoTaskContent content;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求使用的种子整数值。")
|
|
||||||
private Integer seed;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的分辨率。")
|
|
||||||
private String resolution;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的宽高比。")
|
|
||||||
private String ratio;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的时长,单位:秒。与 frames 只会返回其一。")
|
|
||||||
private Integer duration;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的帧数。与 duration 只会返回其一。")
|
|
||||||
private Integer frames;
|
|
||||||
|
|
||||||
@JsonProperty("framespersecond")
|
|
||||||
@ApiModelProperty(value = "生成视频的帧率。")
|
|
||||||
private Integer framesPerSecond;
|
|
||||||
|
|
||||||
@JsonProperty("generate_audio")
|
|
||||||
@ApiModelProperty(value = "生成的视频是否包含与画面同步的声音。仅 Seedance 1.5 pro 返回。")
|
|
||||||
private Boolean generateAudio;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求模型实际使用的工具,未使用工具时不返回。")
|
|
||||||
private List<VideoTaskTool> tools;
|
|
||||||
|
|
||||||
@JsonProperty("safety_identifier")
|
|
||||||
@ApiModelProperty(value = "终端用户的唯一标识符,创建任务时传入则原样返回。")
|
|
||||||
private String safetyIdentifier;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "是否为 Draft 视频。仅 Seedance 1.5 pro 返回。")
|
|
||||||
private Boolean draft;
|
|
||||||
|
|
||||||
@JsonProperty("draft_task_id")
|
|
||||||
@ApiModelProperty(value = "Draft 视频任务 ID,基于 Draft 生成正式视频时返回。")
|
|
||||||
private String draftTaskId;
|
|
||||||
|
|
||||||
@JsonProperty("service_tier")
|
|
||||||
@ApiModelProperty(value = "实际处理任务使用的服务等级。")
|
|
||||||
private String serviceTier;
|
|
||||||
|
|
||||||
@JsonProperty("execution_expires_after")
|
|
||||||
@ApiModelProperty(value = "任务超时阈值,单位:秒。")
|
|
||||||
private Integer executionExpiresAfter;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求的 token 用量。")
|
|
||||||
private VideoGenerationUsage usage;
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoGenerationUsage;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskContent;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskError;
|
|
||||||
import com.ruoyi.common.core.response.video.dto.VideoTaskTool;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "查询视频生成任务 API 返回参数")
|
|
||||||
public class GetVideoGenerationTaskResponse {
|
|
||||||
@ApiModelProperty(value = "视频生成任务 ID。")
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "任务使用的模型名称和版本,模型名称-版本。")
|
|
||||||
private String model;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "任务状态:queued 排队中;running 运行中;cancelled 已取消;succeeded 成功;failed 失败;expired 超时。")
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "错误提示信息,任务成功时为 null。")
|
|
||||||
private VideoTaskError error;
|
|
||||||
|
|
||||||
@JsonProperty("created_at")
|
|
||||||
@ApiModelProperty(value = "任务创建时间的 Unix 时间戳(秒)。")
|
|
||||||
private Integer createdAt;
|
|
||||||
|
|
||||||
@JsonProperty("updated_at")
|
|
||||||
@ApiModelProperty(value = "任务当前状态更新时间的 Unix 时间戳(秒)。")
|
|
||||||
private Integer updatedAt;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "视频生成任务的输出内容。")
|
|
||||||
private VideoTaskContent content;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求使用的种子整数值。")
|
|
||||||
private Integer seed;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的分辨率。")
|
|
||||||
private String resolution;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的宽高比。")
|
|
||||||
private String ratio;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的时长,单位:秒。与 frames 只会返回其一。")
|
|
||||||
private Integer duration;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "生成视频的帧数。与 duration 只会返回其一。")
|
|
||||||
private Integer frames;
|
|
||||||
|
|
||||||
@JsonProperty("framespersecond")
|
|
||||||
@ApiModelProperty(value = "生成视频的帧率。")
|
|
||||||
private Integer framesPerSecond;
|
|
||||||
|
|
||||||
@JsonProperty("generate_audio")
|
|
||||||
@ApiModelProperty(value = "生成的视频是否包含与画面同步的声音。仅 Seedance 1.5 pro 返回。")
|
|
||||||
private Boolean generateAudio;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求模型实际使用的工具,未使用工具时不返回。")
|
|
||||||
private List<VideoTaskTool> tools;
|
|
||||||
|
|
||||||
@JsonProperty("safety_identifier")
|
|
||||||
@ApiModelProperty(value = "终端用户的唯一标识符,创建任务时传入则原样返回。")
|
|
||||||
private String safetyIdentifier;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "是否为 Draft 视频。仅 Seedance 1.5 pro 返回。")
|
|
||||||
private Boolean draft;
|
|
||||||
|
|
||||||
@JsonProperty("draft_task_id")
|
|
||||||
@ApiModelProperty(value = "Draft 视频任务 ID,基于 Draft 生成正式视频时返回。")
|
|
||||||
private String draftTaskId;
|
|
||||||
|
|
||||||
@JsonProperty("service_tier")
|
|
||||||
@ApiModelProperty(value = "实际处理任务使用的服务等级。")
|
|
||||||
private String serviceTier;
|
|
||||||
|
|
||||||
@JsonProperty("execution_expires_after")
|
|
||||||
@ApiModelProperty(value = "任务超时阈值,单位:秒。")
|
|
||||||
private Integer executionExpiresAfter;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "本次请求的 token 用量。")
|
|
||||||
private VideoGenerationUsage usage;
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "使用工具的用量信息")
|
|
||||||
public class UsageToolUsage {
|
|
||||||
@JsonProperty("web_search")
|
|
||||||
@ApiModelProperty(value = "实际调用联网搜索工具的次数,仅开启联网搜索时返回。")
|
|
||||||
private Integer webSearch;
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "本次请求的 token 用量")
|
|
||||||
public class VideoGenerationUsage {
|
|
||||||
@JsonProperty("completion_tokens")
|
|
||||||
@ApiModelProperty(value = "模型输出视频花费的 token 数量。")
|
|
||||||
private Integer completionTokens;
|
|
||||||
|
|
||||||
@JsonProperty("total_tokens")
|
|
||||||
@ApiModelProperty(value = "本次请求消耗的总 token 数量。视频生成不统计输入 token,故 total_tokens 与 completion_tokens 一致。")
|
|
||||||
private Integer totalTokens;
|
|
||||||
|
|
||||||
@JsonProperty("tool_usage")
|
|
||||||
@ApiModelProperty(value = "使用工具的用量信息。")
|
|
||||||
private UsageToolUsage toolUsage;
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video.dto;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "视频生成任务的输出内容")
|
|
||||||
public class VideoTaskContent {
|
|
||||||
@JsonProperty("video_url")
|
|
||||||
@ApiModelProperty(value = "生成视频的 URL,格式为 mp4。为保障信息安全,生成的视频会在 24 小时后被清理,请及时转存。")
|
|
||||||
private String videoUrl;
|
|
||||||
|
|
||||||
@JsonProperty("last_frame_url")
|
|
||||||
@ApiModelProperty(value = "视频的尾帧图像 URL。有效期为 24 小时,请及时转存。创建任务时设置 return_last_frame: true 时返回。")
|
|
||||||
private String lastFrameUrl;
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video.dto;
|
|
||||||
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "视频生成任务错误信息(任务成功时为 null)")
|
|
||||||
public class VideoTaskError {
|
|
||||||
@ApiModelProperty(value = "错误码。")
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
@ApiModelProperty(value = "错误提示信息。")
|
|
||||||
private String message;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package com.ruoyi.common.core.response.video.dto;
|
|
||||||
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ApiModel(description = "本次请求模型实际使用的工具")
|
|
||||||
public class VideoTaskTool {
|
|
||||||
@ApiModelProperty(value = "实际使用的工具类型,例如 web_search(联网搜索)。")
|
|
||||||
private String type;
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package com.ruoyi.common.enums;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AiOrder表中status的类型
|
|
||||||
*/
|
|
||||||
public enum AiOrderStatusType {
|
|
||||||
// 0-进行中
|
|
||||||
RUNNING,
|
|
||||||
// 1-已完成
|
|
||||||
FINISH,
|
|
||||||
// 2-失败(余额退回)
|
|
||||||
FAIL;
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
package com.ruoyi.common.enums;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成视频任务中,状态的枚举
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
public enum VideoTaskStatusType {
|
|
||||||
QUEUED("queued"),
|
|
||||||
|
|
||||||
RUNNING("running"),
|
|
||||||
|
|
||||||
CANCELLED("cancelled"),
|
|
||||||
|
|
||||||
SUCCEEDED("succeeded"),
|
|
||||||
|
|
||||||
FAILED("failed"),
|
|
||||||
|
|
||||||
EXPIRED("expired");
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
private static final Set<String> VALID_NAMES = Arrays.stream(VideoTaskStatusType.values())
|
|
||||||
.map(status -> status.name)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
/**
|
|
||||||
* 判断名称是否正确, 不区分大小写
|
|
||||||
*/
|
|
||||||
public static boolean isValidName(String name) {
|
|
||||||
if (name == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String nameLowerCase = name.toLowerCase();
|
|
||||||
// 直接判断小写后的入参是否在静态Set中
|
|
||||||
return VALID_NAMES.contains(nameLowerCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoTaskStatusType(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -23,7 +23,7 @@ import java.util.UUID;
|
||||||
/**
|
/**
|
||||||
* AWS S3 文件上传工具类
|
* AWS S3 文件上传工具类
|
||||||
*/
|
*/
|
||||||
//@Component
|
@Component
|
||||||
public class AwsS3Util {
|
public class AwsS3Util {
|
||||||
|
|
||||||
// -------------------------- 配置参数(需根据实际环境修改)--------------------------
|
// -------------------------- 配置参数(需根据实际环境修改)--------------------------
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,6 @@
|
||||||
<artifactId>ruoyi-system</artifactId>
|
<artifactId>ruoyi-system</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Redisson 分布式锁(与 Spring Boot 2.5 对齐 3.17.x) -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.redisson</groupId>
|
|
||||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
|
||||||
<version>3.17.7</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -36,10 +36,6 @@ public class AiOrder extends BaseEntity {
|
||||||
@Excel(name = "订单编号")
|
@Excel(name = "订单编号")
|
||||||
private String orderNum;
|
private String orderNum;
|
||||||
|
|
||||||
/** 第三方单号 */
|
|
||||||
@Excel(name = "第三方单号")
|
|
||||||
private String thirdPartyOrderNum;
|
|
||||||
|
|
||||||
/** 用户ID */
|
/** 用户ID */
|
||||||
@Excel(name = "用户ID")
|
@Excel(name = "用户ID")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
@ -48,25 +44,10 @@ public class AiOrder extends BaseEntity {
|
||||||
@Excel(name = "AI类型")
|
@Excel(name = "AI类型")
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
/** 预扣金额 */
|
|
||||||
@Excel(name = "预扣金额")
|
|
||||||
private BigDecimal preDeductAmount;
|
|
||||||
|
|
||||||
/** 金额 */
|
/** 金额 */
|
||||||
@Excel(name = "金额")
|
@Excel(name = "金额")
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
/** 模型Tokens用量 */
|
|
||||||
@Excel(name = "模型Tokens用量")
|
|
||||||
private BigDecimal totalUsage;
|
|
||||||
|
|
||||||
@Excel(name = "是否回补处理过: 0-否 1-是")
|
|
||||||
private Integer isBackfilled;
|
|
||||||
|
|
||||||
/** 生成视频时的请求ID */
|
|
||||||
@Excel(name = "生成视频时的请求ID")
|
|
||||||
private String videoGenRequestId;
|
|
||||||
|
|
||||||
/** 生成结果 */
|
/** 生成结果 */
|
||||||
@Excel(name = "生成结果")
|
@Excel(name = "生成结果")
|
||||||
private String result;
|
private String result;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,6 @@ public class ByteBodyRes {
|
||||||
private Integer duration;
|
private Integer duration;
|
||||||
private Integer framespersecond;
|
private Integer framespersecond;
|
||||||
private boolean draft;
|
private boolean draft;
|
||||||
// 火山请求ID,在head里,需手动设置
|
|
||||||
private String requestId;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import java.util.List;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.ruoyi.ai.domain.AiOrder;
|
import com.ruoyi.ai.domain.AiOrder;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单管理Mapper接口
|
* 订单管理Mapper接口
|
||||||
|
|
@ -27,7 +26,4 @@ public interface AiOrderMapper extends BaseMapper<AiOrder> {
|
||||||
AiOrder getAiOrderByPortalVideoTask(@Param("taskId") String taskId);
|
AiOrder getAiOrderByPortalVideoTask(@Param("taskId") String taskId);
|
||||||
|
|
||||||
BigDecimal getSumAmountByUserId(@Param("userId") String userId);
|
BigDecimal getSumAmountByUserId(@Param("userId") String userId);
|
||||||
|
|
||||||
@Select("SELECT * FROM ai_order WHERE third_party_order_num = #{id} LIMIT 1")
|
|
||||||
AiOrder selectOneByThirdPartyOrderNum(@Param("id") String id);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,6 @@ import java.util.List;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.ruoyi.ai.domain.AiOrder;
|
import com.ruoyi.ai.domain.AiOrder;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.core.request.video.dto.VideoTaskCallBackRequest;
|
|
||||||
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单管理Service接口
|
* 订单管理Service接口
|
||||||
|
|
@ -75,29 +71,15 @@ public interface IAiOrderService {
|
||||||
*/
|
*/
|
||||||
int deleteAiOrderById(Long id);
|
int deleteAiOrderById(Long id);
|
||||||
|
|
||||||
AiOrder getAiOrder(String aiType, boolean isReduceBalance);
|
|
||||||
|
|
||||||
AiOrder getAiOrder(String aiType);
|
AiOrder getAiOrder(String aiType);
|
||||||
|
|
||||||
void orderFailure(AiOrder aiOrder);
|
void orderFailure(AiOrder aiOrder);
|
||||||
|
|
||||||
@Transactional
|
|
||||||
void orderFailure(AiOrder aiOrder, BigDecimal amount);
|
|
||||||
|
|
||||||
void orderSuccess(AiOrder aiOrder);
|
void orderSuccess(AiOrder aiOrder);
|
||||||
|
|
||||||
AiOrder getAiOrderByResult(String result);
|
AiOrder getAiOrderByResult(String result);
|
||||||
|
|
||||||
AiOrder getAiOrderByPortalVideoTask(String taskId);
|
AiOrder getAiOrderByPortalVideoTask(String taskId);
|
||||||
|
|
||||||
int getChangerType(String aiType);
|
|
||||||
|
|
||||||
BigDecimal getSumAmountByUserId(String userId);
|
BigDecimal getSumAmountByUserId(String userId);
|
||||||
|
|
||||||
AiOrder selectOneByThirdPartyOrderNum(String id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 火山回调 - 任务成功时处理流程
|
|
||||||
*/
|
|
||||||
AjaxResult volcCallbackSuccessProcess(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp, AiOrder order);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package com.ruoyi.ai.service;
|
package com.ruoyi.ai.service;
|
||||||
|
|
||||||
import com.ruoyi.ai.domain.AiOrder;
|
|
||||||
import com.ruoyi.ai.domain.ByteBodyReq;
|
import com.ruoyi.ai.domain.ByteBodyReq;
|
||||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
import com.ruoyi.ai.domain.ByteBodyRes;
|
||||||
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.response.video.GetVideoGenerationTaskResponse;
|
|
||||||
|
|
||||||
public interface IByteService {
|
public interface IByteService {
|
||||||
|
|
||||||
|
|
@ -47,15 +44,4 @@ public interface IByteService {
|
||||||
* GET 查询视频生成任务列表(火山 list 文档),返回原始 JSON 字符串
|
* GET 查询视频生成任务列表(火山 list 文档),返回原始 JSON 字符串
|
||||||
*/
|
*/
|
||||||
String listVideoGenerationTasks(int pageNum, int pageSize, String arkApiKey) throws Exception;
|
String listVideoGenerationTasks(int pageNum, int pageSize, String arkApiKey) throws Exception;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前用户所使用的api key
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
String resolveCurrentAiUserApiKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET 查询视频生成任务(单个)
|
|
||||||
*/
|
|
||||||
GetVideoGenerationTaskResponse getVideoGenerationTasks(String thirdPartyOrderNumId, String arkApiKey) throws Exception;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,11 @@ import com.ruoyi.ai.service.IAiStatisticsService;
|
||||||
import com.ruoyi.ai.service.IAiUserService;
|
import com.ruoyi.ai.service.IAiUserService;
|
||||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
import com.ruoyi.common.constant.BalanceChangerConstants;
|
||||||
import com.ruoyi.common.constant.HttpStatus;
|
import com.ruoyi.common.constant.HttpStatus;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||||
import com.ruoyi.common.core.request.video.dto.VideoTaskCallBackRequest;
|
|
||||||
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
|
||||||
import com.ruoyi.common.enums.AiOrderStatusType;
|
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
import com.ruoyi.common.utils.MessageUtils;
|
import com.ruoyi.common.utils.MessageUtils;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
@ -42,7 +36,6 @@ import java.util.UUID;
|
||||||
* @author shi
|
* @author shi
|
||||||
* @date 2025-11-13
|
* @date 2025-11-13
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Service
|
@Service
|
||||||
public class AiOrderServiceImpl implements IAiOrderService {
|
public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
|
|
||||||
|
|
@ -58,11 +51,6 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IAiStatisticsService aiStatisticsService;
|
private IAiStatisticsService aiStatisticsService;
|
||||||
|
|
||||||
// 流水表:任务成功时回补
|
|
||||||
private static final String TASK_SUCCESS_BACK_FILL_REMARK = "order.number.generation.successbackfill";
|
|
||||||
|
|
||||||
// 流水表:提交任务时预扣
|
|
||||||
private static final String TASK_SUBMIT_REMARK = "order.number.generation.submit";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询订单管理
|
* 查询订单管理
|
||||||
|
|
@ -148,24 +136,15 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
return aiOrderMapper.deleteAiOrderById(id);
|
return aiOrderMapper.deleteAiOrderById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成订单
|
|
||||||
* @param aiType 对应的AI类型
|
|
||||||
*/
|
|
||||||
public AiOrder getAiOrder(String aiType) {
|
|
||||||
return getAiOrder(aiType, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成订单
|
* 生成订单
|
||||||
*
|
*
|
||||||
* @param aiType 对应的AI类型
|
* @param aiType 对应的AI类型
|
||||||
* @param isReduceBalance 是否扣减账户
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public AiOrder getAiOrder(String aiType, boolean isReduceBalance) {
|
public AiOrder getAiOrder(String aiType) {
|
||||||
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
|
AiManager aiManager = aiManagerService.selectAiManagerByType(aiType);
|
||||||
if (aiManager == null) {
|
if (aiManager == null) {
|
||||||
throw new ServiceException(
|
throw new ServiceException(
|
||||||
|
|
@ -185,49 +164,24 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
aiOrder.setUserId(SecurityUtils.getAiUserId());
|
aiOrder.setUserId(SecurityUtils.getAiUserId());
|
||||||
aiOrder.setType(aiType);
|
aiOrder.setType(aiType);
|
||||||
aiOrder.setResult(null);
|
aiOrder.setResult(null);
|
||||||
if (isReduceBalance) {
|
|
||||||
aiOrder.setPreDeductAmount(aiManager.getPrice());
|
|
||||||
aiOrder.setAmount(aiManager.getPrice());
|
aiOrder.setAmount(aiManager.getPrice());
|
||||||
} else {
|
|
||||||
// 不按aimanager扣减的,等提交任务再按实扣减
|
|
||||||
aiOrder.setAmount(new BigDecimal(0));
|
|
||||||
}
|
|
||||||
aiOrder.setStatus(0);
|
aiOrder.setStatus(0);
|
||||||
aiOrder.setSource(aiUser.getSource());
|
aiOrder.setSource(aiUser.getSource());
|
||||||
aiOrderMapper.insert(aiOrder);
|
aiOrderMapper.insert(aiOrder);
|
||||||
// 执行余额变更
|
// 执行余额变更
|
||||||
if (isReduceBalance) {
|
aiUserService.addUserBalance(orderno, SecurityUtils.getAiUserId(), NumberUtil.mul(-1, aiManager.getPrice()), getChangerType(aiType));
|
||||||
String remark = MessageUtils.message(TASK_SUBMIT_REMARK, aiOrder.getOrderNum());
|
|
||||||
aiUserService.addUserBalance(orderno, SecurityUtils.getAiUserId()
|
|
||||||
, NumberUtil.mul(-1, aiManager.getPrice()), getChangerType(aiType), remark);
|
|
||||||
}
|
|
||||||
return aiOrder;
|
return aiOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单失败时,返回订单amount对应数量的用量
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void orderFailure(AiOrder aiOrder) {
|
|
||||||
orderFailure(aiOrder, aiOrder.getAmount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单失败时,返回指定用量的用量
|
|
||||||
*/
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
public void orderFailure(AiOrder aiOrder) {
|
||||||
public void orderFailure(AiOrder aiOrder, BigDecimal amount) {
|
|
||||||
aiOrder.setIsBackfilled(1);
|
|
||||||
aiOrder.setStatus(2);
|
aiOrder.setStatus(2);
|
||||||
String remark = MessageUtils.message("order.number.generation.failed", aiOrder.getOrderNum());
|
String remark = MessageUtils.message("order.number.generation.failed", aiOrder.getOrderNum());
|
||||||
aiOrder.setRemark(remark);
|
aiOrder.setRemark(remark);
|
||||||
aiOrderMapper.updateById(aiOrder);
|
aiOrderMapper.updateById(aiOrder);
|
||||||
Long userId = aiOrder.getUserId() != null ? aiOrder.getUserId() : SecurityUtils.getAiUserId();
|
Long userId = aiOrder.getUserId() != null ? aiOrder.getUserId() : SecurityUtils.getAiUserId();
|
||||||
// 变更值为0则不改余额,没有流水
|
aiUserService.addUserBalance(aiOrder.getOrderNum(), userId, aiOrder.getAmount(), BalanceChangerConstants.REFUND, remark);
|
||||||
if (aiOrder.getAmount() != null && !aiOrder.getAmount().equals(new BigDecimal(0))) {
|
|
||||||
aiUserService.addUserBalance(aiOrder.getOrderNum(), userId, amount, BalanceChangerConstants.REFUND, remark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -255,7 +209,6 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
return aiOrderMapper.getAiOrderByPortalVideoTask(taskId);
|
return aiOrderMapper.getAiOrderByPortalVideoTask(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getChangerType(String aiType) {
|
public int getChangerType(String aiType) {
|
||||||
switch (aiType) {
|
switch (aiType) {
|
||||||
case "11":
|
case "11":
|
||||||
|
|
@ -277,67 +230,4 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
public BigDecimal getSumAmountByUserId(String userId) {
|
public BigDecimal getSumAmountByUserId(String userId) {
|
||||||
return aiOrderMapper.getSumAmountByUserId(userId);
|
return aiOrderMapper.getSumAmountByUserId(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public AiOrder selectOneByThirdPartyOrderNum(String id) {
|
|
||||||
return aiOrderMapper.selectOneByThirdPartyOrderNum(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public AjaxResult volcCallbackSuccessProcess(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
|
||||||
// 用量是否一致
|
|
||||||
Integer requestTotalTokens = request.getUsage() == null || request.getUsage().getTotalTokens() == null
|
|
||||||
? 0 : request.getUsage().getTotalTokens();
|
|
||||||
Integer officialTotalTokens = taskResp.getUsage() == null || taskResp.getUsage().getTotalTokens() == null
|
|
||||||
? 0 : taskResp.getUsage().getTotalTokens();
|
|
||||||
// !!!注意!!!
|
|
||||||
// 以下检查不成立直接返回error,事务不会回退,所以不要在检查前面有存库操作
|
|
||||||
// 这两个检查只是以防万一的,防止恶意回调,如果出现允许数据库中存在异常单,后续由程序通过日志检查问题
|
|
||||||
if (!requestTotalTokens.equals(officialTotalTokens)) {
|
|
||||||
log.error("volcCallback request's total tokens != official tokens! third party order num = {}, request's tokens = {}, official tokens = {}",
|
|
||||||
request.getId(), requestTotalTokens, officialTotalTokens);
|
|
||||||
return AjaxResult.error();
|
|
||||||
}
|
|
||||||
if (officialTotalTokens <= 0) {
|
|
||||||
// 异常情况,应该不会出现,以防万一
|
|
||||||
log.error("volcCallback official tokens <= 0! third party order num = {}, request's tokens = {}, official tokens = {}",
|
|
||||||
request.getId(), requestTotalTokens, officialTotalTokens);
|
|
||||||
return AjaxResult.error();
|
|
||||||
}
|
|
||||||
BigDecimal realAmount = new BigDecimal(officialTotalTokens);
|
|
||||||
// 先存库再回补,防止订单保存时,会一直回补给用户
|
|
||||||
// 设置视频地址与状态
|
|
||||||
if (taskResp.getContent() != null && StringUtils.isNotEmpty(taskResp.getContent().getVideoUrl())) {
|
|
||||||
order.setResult(taskResp.getContent().getVideoUrl());
|
|
||||||
}
|
|
||||||
// 设置用量
|
|
||||||
order.setAmount(realAmount);
|
|
||||||
// tokens用量
|
|
||||||
order.setTotalUsage(realAmount);
|
|
||||||
// 订单状态
|
|
||||||
order.setStatus(AiOrderStatusType.FINISH.ordinal());
|
|
||||||
// 已回补
|
|
||||||
order.setIsBackfilled(1);
|
|
||||||
orderSuccess(order);
|
|
||||||
|
|
||||||
// 用量回补、多退少补 = 预扣量 - 实际用量
|
|
||||||
// 有预扣值才回补,没有的是历史单,不处理
|
|
||||||
if (order.getPreDeductAmount() != null
|
|
||||||
&& order.getPreDeductAmount().compareTo(new BigDecimal(0)) > 0) {
|
|
||||||
BigDecimal addAmount = order.getPreDeductAmount().subtract(realAmount);
|
|
||||||
if (addAmount.compareTo(new BigDecimal(0)) != 0) {
|
|
||||||
// 回补
|
|
||||||
String remark = MessageUtils.message(TASK_SUCCESS_BACK_FILL_REMARK, order.getOrderNum());
|
|
||||||
aiUserService.addUserBalance(order.getOrderNum(), order.getUserId(), addAmount,
|
|
||||||
BalanceChangerConstants.REFUND, remark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按实际用量扣减
|
|
||||||
// BigDecimal reduceAmount = NumberUtil.mul(-1, realAmount);
|
|
||||||
// aiUserService.addUserBalance(order.getOrderNum(), order.getUserId(), reduceAmount,
|
|
||||||
// BalanceChangerConstants.QUICK_VIDEO_GENERATION, TASK_SUCCESS_BALANCE_REMARK);
|
|
||||||
return AjaxResult.success("callback success");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,11 @@ package com.ruoyi.ai.service.impl;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.ai.domain.AiOrder;
|
|
||||||
import com.ruoyi.ai.domain.ByteBodyReq;
|
import com.ruoyi.ai.domain.ByteBodyReq;
|
||||||
import com.ruoyi.ai.domain.ByteBodyRes;
|
import com.ruoyi.ai.domain.ByteBodyRes;
|
||||||
import com.ruoyi.ai.service.IAiUserService;
|
|
||||||
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
||||||
import com.ruoyi.ai.service.IByteService;
|
import com.ruoyi.ai.service.IByteService;
|
||||||
import com.ruoyi.common.constant.BalanceChangerConstants;
|
|
||||||
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.response.video.GetVideoGenerationTaskResponse;
|
|
||||||
import com.ruoyi.common.enums.AiOrderStatusType;
|
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||||
|
|
@ -23,9 +17,6 @@ import okhttp3.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -130,12 +121,8 @@ public class ByteService implements IByteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String responseBody = response.body().string();
|
String responseBody = response.body().string();
|
||||||
String requestId = response.header("x-request-id");
|
log.info("调用火山接口, response = {}", responseBody);
|
||||||
log.info("调用火山接口, requestId = {}, response = {}", requestId, responseBody);
|
return objectMapper.readValue(responseBody, ByteBodyRes.class);
|
||||||
ByteBodyRes resp = objectMapper.readValue(responseBody, ByteBodyRes.class);
|
|
||||||
// 从headder拿到requestId,便于联调
|
|
||||||
resp.setRequestId(requestId);
|
|
||||||
return resp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -244,36 +231,7 @@ public class ByteService implements IByteService {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String resolveCurrentAiUserApiKey() {
|
private String resolveCurrentAiUserApiKey() {
|
||||||
return byteDeptApiKeyService.resolveVolcApiKey(SecurityUtils.getAiUserId());
|
return byteDeptApiKeyService.resolveVolcApiKey(SecurityUtils.getAiUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public GetVideoGenerationTaskResponse getVideoGenerationTasks(String thirdPartyOrderNumId, String arkApiKey) throws Exception {
|
|
||||||
if (StringUtils.isBlank(arkApiKey)) {
|
|
||||||
throw new Exception("getVideoGenerationTasks error:apiKey is null");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(thirdPartyOrderNumId)) {
|
|
||||||
throw new Exception("getVideoGenerationTasks error:thirdPartyOrderNumId is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpUrl parsed = HttpUrl.parse(volcBaseUrl + "/api/v3/contents/generations/tasks/" + thirdPartyOrderNumId);
|
|
||||||
if (parsed == null) {
|
|
||||||
throw new Exception("listVideoGenerationTasks error:invalid base url");
|
|
||||||
}
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(parsed)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Authorization", "Bearer " + arkApiKey)
|
|
||||||
.get().build();
|
|
||||||
Response response = OkHttpUtils.newCall(request).execute();
|
|
||||||
if (response.body() == null) {
|
|
||||||
throw new Exception("listVideoGenerationTasks response null");
|
|
||||||
}
|
|
||||||
String body = response.body().string();
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
throw new Exception("listVideoGenerationTasks error:" + body);
|
|
||||||
}
|
|
||||||
return objectMapper.readValue(body, GetVideoGenerationTaskResponse.class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<result property="updateBy" column="update_by" />
|
<result property="updateBy" column="update_by" />
|
||||||
<result property="updateTime" column="update_time" />
|
<result property="updateTime" column="update_time" />
|
||||||
<result property="remark" column="remark" />
|
<result property="remark" column="remark" />
|
||||||
<result property="thirdPartyOrderNum" column="third_party_order_num" />
|
|
||||||
<result property="orderNum" column="order_num" />
|
<result property="orderNum" column="order_num" />
|
||||||
<result property="userId" column="user_id" />
|
<result property="userId" column="user_id" />
|
||||||
<result property="type" column="type" />
|
<result property="type" column="type" />
|
||||||
<result property="preDeductAmount" column="pre_deduct_amount" />
|
|
||||||
<result property="amount" column="amount" />
|
<result property="amount" column="amount" />
|
||||||
<result property="totalUsage" column="total_usage" />
|
|
||||||
<result property="result" column="result" />
|
<result property="result" column="result" />
|
||||||
<result property="status" column="status" />
|
<result property="status" column="status" />
|
||||||
<result property="source" column="source" />
|
<result property="source" column="source" />
|
||||||
|
|
@ -32,19 +29,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
<result property="ratio" column="ratio" />
|
<result property="ratio" column="ratio" />
|
||||||
<result property="model" column="model" />
|
<result property="model" column="model" />
|
||||||
<result property="videoParams" column="video_params" />
|
<result property="videoParams" column="video_params" />
|
||||||
<result property="isBackfilled" column="is_backfilled" />
|
|
||||||
<result property="videoGenRequestId" column="video_gen_request_id" />
|
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectAiOrderVo">
|
<sql id="selectAiOrderVo">
|
||||||
select ao.id, ao.del_flag, ao.create_by, ao.create_time, ao.update_by, ao.update_time, ao.remark, ao.order_num, ao.third_party_order_num, ao.user_id, ao.type, ao.pre_deduct_amount, ao.amount, ao.total_usage, ao.result, ao.status, ao.source, ao.text, ao.is_top, ao.img1, ao.img2, ao.mode, ao.duration, ao.resolution, ao.ratio, ao.model, ao.video_params, au.user_id uuid, ao.ext_status, ao.is_backfilled, ao.video_gen_request_id from ai_order ao
|
select ao.id, ao.del_flag, ao.create_by, ao.create_time, ao.update_by, ao.update_time, ao.remark, ao.order_num, ao.user_id, ao.type, ao.amount, ao.result, ao.status, ao.source, ao.text, ao.is_top, ao.img1, ao.img2, ao.mode, ao.duration, ao.resolution, ao.ratio, ao.model, ao.video_params, au.user_id uuid, ao.ext_status from ai_order ao
|
||||||
left join ai_user au on au.id = ao.user_id
|
left join ai_user au on au.id = ao.user_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="selectAiOrderList" parameterType="AiOrder" resultMap="AiOrderResult">
|
<select id="selectAiOrderList" parameterType="AiOrder" resultMap="AiOrderResult">
|
||||||
<include refid="selectAiOrderVo"/>
|
<include refid="selectAiOrderVo"/>
|
||||||
<where>
|
<where>
|
||||||
<if test="orderNum != null and orderNum != ''"> and ao.order_num like concat('%', #{orderNum}, '%')</if>
|
<if test="orderNum != null and orderNum != ''"> and ao.orderNum like concat('%', #{orderNum}, '%')</if>
|
||||||
<if test="text != null and text != ''"> and ao.text like concat('%', #{text}, '%')</if>
|
<if test="text != null and text != ''"> and ao.text like concat('%', #{text}, '%')</if>
|
||||||
<if test="userId != null "> and ao.user_id = #{userId}</if>
|
<if test="userId != null "> and ao.user_id = #{userId}</if>
|
||||||
<if test="uuid != null "> and au.user_id = #{uuid}</if>
|
<if test="uuid != null "> and au.user_id = #{uuid}</if>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue