fix: 视频生成问题
This commit is contained in:
parent
4ea4d009a6
commit
37f3237f61
|
|
@ -6,3 +6,6 @@ ENV = 'production'
|
|||
|
||||
# 若依管理系统/生产环境
|
||||
VUE_APP_BASE_API = 'http://111.230.37.169:10009'
|
||||
# VUE_APP_BASE_API = 'http://101.96.201.225:8011'
|
||||
# VUE_APP_BASE_API = 'http://47.86.170.114:8011'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,5 +8,4 @@ NODE_ENV = production
|
|||
# 测试环境配置
|
||||
ENV = 'staging'
|
||||
|
||||
# 若依管理系统/测试环境
|
||||
VUE_APP_BASE_API = '/api'
|
||||
VUE_APP_BASE_API = 'http://101.96.201.225:8011'
|
||||
|
|
|
|||
|
|
@ -67,6 +67,27 @@
|
|||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['ai:user:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete()"
|
||||
v-hasPermi="['ai:user:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
|
|
@ -114,7 +135,7 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="余额" align="center" prop="balance" />
|
||||
<el-table-column label="source" align="center" prop="source" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="250">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="310">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
|
|
@ -137,6 +158,13 @@
|
|||
@click="updateBalance(scope.row)"
|
||||
v-hasPermi="['ai:user:remove']"
|
||||
>修改余额</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['ai:user:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
@ -150,8 +178,24 @@
|
|||
/>
|
||||
|
||||
<!-- 添加或修改ai-用户信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-dialog :title="title" :visible.sync="open" width="520px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="88px">
|
||||
<el-form-item label="用户账号" prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="门户登录账号,必填"
|
||||
:disabled="form.id != null"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="form.id != null ? '不填表示不修改密码' : '请输入登录密码'"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input v-model="form.nickname" placeholder="请输入用户昵称" />
|
||||
</el-form-item>
|
||||
|
|
@ -176,10 +220,10 @@
|
|||
<el-form-item label="余额" prop="balance">
|
||||
<el-input v-model="form.balance" placeholder="请输入余额" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
|
|
@ -307,11 +351,18 @@ export default {
|
|||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
delFlag: [
|
||||
{ required: true, message: "删除标志不能为空", trigger: "blur" }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: "手机号码不能为空", trigger: "blur" }
|
||||
username: [{ required: true, message: "用户账号不能为空", trigger: "blur" }],
|
||||
password: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (this.form.id == null && (!value || !String(value).trim())) {
|
||||
callback(new Error("密码不能为空"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
@ -442,6 +493,7 @@ export default {
|
|||
updateBy: null,
|
||||
updateTime: null,
|
||||
remark: null,
|
||||
username: null,
|
||||
nickname: null,
|
||||
gender: null,
|
||||
avatar: null,
|
||||
|
|
@ -496,13 +548,17 @@ export default {
|
|||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
updateUser(this.form).then(response => {
|
||||
const payload = { ...this.form };
|
||||
if (!payload.password || !String(payload.password).trim()) {
|
||||
delete payload.password;
|
||||
}
|
||||
updateUser(payload).then(() => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
addUser(this.form).then(response => {
|
||||
addUser(this.form).then(() => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
|
|
@ -523,11 +579,16 @@ export default {
|
|||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.id || this.ids;
|
||||
const ids = row && row.id != null ? row.id : this.ids;
|
||||
if (ids == null || (Array.isArray(ids) && !ids.length)) {
|
||||
this.$modal.msgWarning("请选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
const idParam = Array.isArray(ids) ? ids.join(",") : ids;
|
||||
this.$modal
|
||||
.confirm('是否确认删除ai-用户信息编号为"' + ids + '"的数据项?')
|
||||
.then(function() {
|
||||
return delUser(ids);
|
||||
.confirm('是否确认删除ai-用户信息编号为"' + idParam + '"的数据项?')
|
||||
.then(() => {
|
||||
return delUser(idParam);
|
||||
})
|
||||
.then(() => {
|
||||
this.getList();
|
||||
|
|
|
|||
|
|
@ -581,10 +581,14 @@ export default {
|
|||
},
|
||||
|
||||
taskStatusText(row) {
|
||||
if (row.status === 1 && row.result && this.isHttpOrHttpsUrl(row.result)) return '已完成'
|
||||
if (row.status === 1) return '执行任务中'
|
||||
// 与后端约定:0 执行中,2 失败,1+http(s) 成功,1 且非 http 为生成失败
|
||||
if (row.status === 2) return '已失败/已取消'
|
||||
return '进行中'
|
||||
if (row.status === 0) return '执行中'
|
||||
if (row.status === 1) {
|
||||
if (row.result && this.isHttpOrHttpsUrl(row.result)) return '已完成'
|
||||
return '任务生成失败'
|
||||
}
|
||||
return '执行中'
|
||||
},
|
||||
|
||||
/** 是否展示完整「结果区」(视频 / 链接);其余状态仅紧凑展示在用户行右侧 */
|
||||
|
|
@ -594,6 +598,7 @@ export default {
|
|||
|
||||
chatRowInlineStatusClass(row) {
|
||||
if (row.status === 2) return 'vg-chat-inline-status--failed'
|
||||
if (row.status === 1 && !this.isHttpOrHttpsUrl(row.result)) return 'vg-chat-inline-status--failed'
|
||||
return 'vg-chat-inline-status--running'
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -327,30 +327,105 @@ public class ByteApiController extends BaseController {
|
|||
return AjaxResult.success(byteBodyRes);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/volcCallback")
|
||||
@PostMapping(value = "/volcCallback")
|
||||
@ApiOperation("火山引擎视频回调")
|
||||
@Anonymous
|
||||
public AjaxResult volcCallback(@RequestBody ByteBodyRes byteBodyRes) throws Exception {
|
||||
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
||||
String id = byteBodyRes.getId();
|
||||
logger.info("volcCallback 收到回调数据: {}", byteBodyRes);
|
||||
String id = byteBodyRes.getId();
|
||||
if (StringUtils.isEmpty(id)) {
|
||||
logger.warn("volcCallback 无任务 id,跳过业务处理");
|
||||
return AjaxResult.success("callback success");
|
||||
}
|
||||
|
||||
Integer code = byteBodyRes.getCode();
|
||||
boolean codeError = code != null && code != 200;
|
||||
String st = byteBodyRes.getStatus();
|
||||
|
||||
if ("running".equals(st) && !codeError) {
|
||||
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 = aiOrderService.getAiOrderByResult(id);
|
||||
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,不退款。
|
||||
*/
|
||||
private void markVolcCallbackOrderClearResultFailed(String taskId, String reason) {
|
||||
AiOrder order = findAiOrderByVolcTaskId(taskId);
|
||||
if (order == null) {
|
||||
logger.warn("volcCallback code 非 200:未找到任务对应订单, taskId={}, {}", taskId, reason);
|
||||
return;
|
||||
}
|
||||
AiOrder upd = new AiOrder();
|
||||
upd.setId(order.getId());
|
||||
upd.setResult("");
|
||||
upd.setStatus(2);
|
||||
aiOrderService.updateAiOrder(upd);
|
||||
logger.warn("volcCallback code 非 200,已清空 result 并 status=2, orderId={}, {}", order.getId(), reason);
|
||||
}
|
||||
|
||||
private AiOrder findAiOrderByVolcTaskId(String taskId) {
|
||||
AiOrder order = aiOrderService.getAiOrderByPortalVideoTask(taskId);
|
||||
if (order != null) {
|
||||
return order;
|
||||
}
|
||||
return aiOrderService.getAiOrderByResult(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调判定任务失败:订单仍持有火山任务 id、且非已失败时,标记失败并退余额(orderFailure)。
|
||||
*/
|
||||
private void handleVolcCallbackFailure(String taskId, String reason) {
|
||||
AiOrder order = aiOrderService.getAiOrderByResult(taskId);
|
||||
if (order == null) {
|
||||
logger.warn("volcCallback 失败:未找到 result={} 的订单, {}", taskId, reason);
|
||||
return;
|
||||
}
|
||||
if (!taskId.equals(order.getResult())) {
|
||||
logger.info("volcCallback 失败处理跳过:订单结果已更新, taskId={}, {}", taskId, reason);
|
||||
return;
|
||||
}
|
||||
if (Integer.valueOf(2).equals(order.getStatus())) {
|
||||
return;
|
||||
}
|
||||
aiOrderService.orderFailure(order);
|
||||
logger.warn("volcCallback 任务失败,已标记订单失败并退款, taskId={}, reason={}", taskId, reason);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{id}/cancel")
|
||||
@ApiOperation("取消视频生成任务")
|
||||
public AjaxResult cancelTask(@PathVariable("id") String id) throws Exception {
|
||||
|
|
|
|||
|
|
@ -484,7 +484,24 @@ public class PortalVideoController extends BaseController {
|
|||
return AjaxResult.error("无权查看该任务");
|
||||
}
|
||||
String key = apiKey();
|
||||
ByteBodyRes byteBodyRes = byteService.uploadVideo(taskId, key);
|
||||
ByteBodyRes byteBodyRes;
|
||||
try {
|
||||
byteBodyRes = byteService.uploadVideo(taskId, key);
|
||||
} catch (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
// ByteService 在 HTTP 非 2xx 时抛出以此前缀开头的异常,仅此种情况清空订单,避免网络抖动等误伤
|
||||
if (msg != null && msg.startsWith("uploadVideo error")) {
|
||||
logger.warn("查询火山任务 HTTP 非成功,已清空订单 result 并标记失败, orderId={}, taskId={}",
|
||||
owned.getId(), taskId, e);
|
||||
AiOrder failedOrder = new AiOrder();
|
||||
failedOrder.setId(owned.getId());
|
||||
failedOrder.setResult("");
|
||||
failedOrder.setStatus(2);
|
||||
aiOrderService.updateAiOrder(failedOrder);
|
||||
return AjaxResult.error(msg);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if ("succeeded".equals(byteBodyRes.getStatus())) {
|
||||
content contentObj = byteBodyRes.getContent();
|
||||
if (contentObj != null && StringUtils.isNotEmpty(contentObj.getVideo_url())) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
|||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
|
@ -54,8 +54,8 @@ public class AiUser extends BaseEntity {
|
|||
@Excel(name = "手机号码")
|
||||
private String phone;
|
||||
|
||||
/** 密码 */
|
||||
@JsonIgnore
|
||||
/** 密码(仅接受入参反序列化,响应 Json 不输出,避免与 @JsonIgnore 导致入参丢失) */
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
@Excel(name = "密码")
|
||||
private String password;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ public class ByteBodyRes {
|
|||
private ByteUsageRes usage;
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 部分错误响应携带的业务/HTTP类状态码;非 200 视为失败(与 status 同时判定时以失败告终)
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* running生成中 succeeded已完成
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -180,7 +180,8 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
|||
String remark = MessageUtils.message("order.number.generation.failed", aiOrder.getOrderNum());
|
||||
aiOrder.setRemark(remark);
|
||||
aiOrderMapper.updateById(aiOrder);
|
||||
aiUserService.addUserBalance(aiOrder.getOrderNum(), SecurityUtils.getAiUserId(), aiOrder.getAmount(), BalanceChangerConstants.REFUND, remark);
|
||||
Long userId = aiOrder.getUserId() != null ? aiOrder.getUserId() : SecurityUtils.getAiUserId();
|
||||
aiUserService.addUserBalance(aiOrder.getOrderNum(), userId, aiOrder.getAmount(), BalanceChangerConstants.REFUND, remark);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -114,6 +114,25 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
*/
|
||||
@Override
|
||||
public int insertAiUser(AiUser aiUser) {
|
||||
if (StringUtils.isEmpty(aiUser.getUsername())) {
|
||||
throw new ServiceException("用户账号不能为空", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (StringUtils.isEmpty(aiUser.getPassword())) {
|
||||
throw new ServiceException("密码不能为空", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (getUserCountByUserName(aiUser.getUsername()) > 0) {
|
||||
throw new ServiceException(MessageUtils.message("user.username.exists", aiUser.getUsername()), HttpStatus.CONFLICT);
|
||||
}
|
||||
aiUser.setPassword(SecurityUtils.encryptPassword(aiUser.getPassword()));
|
||||
if (StringUtils.isEmpty(aiUser.getUserId())) {
|
||||
aiUser.setUserId(generateUuiD());
|
||||
}
|
||||
if (StringUtils.isEmpty(aiUser.getInvitationCode())) {
|
||||
aiUser.setInvitationCode(generateUniqueString());
|
||||
}
|
||||
if (aiUser.getStatus() == null) {
|
||||
aiUser.setStatus(0);
|
||||
}
|
||||
aiUser.setDelFlag("0");
|
||||
aiUser.setCreateBy(SecurityUtils.getUsername());
|
||||
aiUser.setCreateTime(DateUtils.getNowDate());
|
||||
|
|
@ -129,6 +148,11 @@ public class AiUserServiceImpl implements IAiUserService {
|
|||
@Override
|
||||
public int updateAiUser(AiUser aiUser) {
|
||||
aiUser.setUpdateTime(DateUtils.getNowDate());
|
||||
if (StringUtils.isNotEmpty(aiUser.getPassword())) {
|
||||
aiUser.setPassword(SecurityUtils.encryptPassword(aiUser.getPassword()));
|
||||
} else {
|
||||
aiUser.setPassword(null);
|
||||
}
|
||||
return aiUserMapper.updateById(aiUser);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue