fix: 生成视频、任务回调 - 检查代码并修改相关逻辑问题
This commit is contained in:
parent
80c136ad18
commit
f53f849ec8
|
|
@ -12,6 +12,7 @@ import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
||||||
import com.ruoyi.common.enums.AiOrderStatusType;
|
import com.ruoyi.common.enums.AiOrderStatusType;
|
||||||
import com.ruoyi.common.enums.VideoTaskStatusType;
|
import com.ruoyi.common.enums.VideoTaskStatusType;
|
||||||
import com.ruoyi.common.utils.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 io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
|
|
@ -43,19 +44,17 @@ public class ByteApiController extends BaseController {
|
||||||
// 锁参数
|
// 锁参数
|
||||||
private static final int LOCK_WAIT_SECONDS = 10;
|
private static final int LOCK_WAIT_SECONDS = 10;
|
||||||
private static final int LOCK_LEASE_SECONDS = 20;
|
private static final int LOCK_LEASE_SECONDS = 20;
|
||||||
// 任务成功时流水表备注
|
|
||||||
private static final String TASK_SUCCESS_BALANCE_REMARK = "order.number.generation.successes";
|
|
||||||
|
|
||||||
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 IAiUserService aiUserService;
|
|
||||||
private final RedissonClient redissonClient;
|
private final RedissonClient redissonClient;
|
||||||
|
|
||||||
@Value("${volcengine.ark.callbackUrl}")
|
@Value("${volcengine.ark.callbackUrl}")
|
||||||
private String volcCallbackUrl;
|
private String volcCallbackUrl;
|
||||||
|
private final IByteDeptApiKeyService byteDeptApiKeyService;
|
||||||
|
|
||||||
@PostMapping("/promptToImg")
|
@PostMapping("/promptToImg")
|
||||||
@ApiOperation("文生图")
|
@ApiOperation("文生图")
|
||||||
|
|
@ -346,16 +345,25 @@ public class ByteApiController extends BaseController {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 2、从官方获取任务数据
|
// 2、查询订单
|
||||||
String apiKey = byteService.resolveCurrentAiUserApiKey();
|
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);
|
GetVideoGenerationTaskResponse taskResp = byteService.getVideoGenerationTasks(request.getId(), apiKey);
|
||||||
// 3、官方数据校验
|
// 4、官方数据校验
|
||||||
result = volcCallbackByteCheck(request, taskResp);
|
result = volcCallbackByteCheck(request, taskResp);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 4、查询订单(同 taskId 串行:Redisson 分布式锁;步骤 1~3 已在锁外)
|
// 5、查询订单(同 taskId 串行:Redisson 分布式锁;步骤 1~3 已在锁外)
|
||||||
String taskId = request.getId();
|
|
||||||
String lockKey = VOLC_CALLBACK_LOCK_KEY_PREFIX + taskId;
|
String lockKey = VOLC_CALLBACK_LOCK_KEY_PREFIX + taskId;
|
||||||
RLock lock = redissonClient.getLock(lockKey);
|
RLock lock = redissonClient.getLock(lockKey);
|
||||||
boolean locked = false;
|
boolean locked = false;
|
||||||
|
|
@ -365,30 +373,32 @@ public class ByteApiController extends BaseController {
|
||||||
logger.warn("volcCallback skip: concurrent handling for same task, third party order num = {}", taskId);
|
logger.warn("volcCallback skip: concurrent handling for same task, third party order num = {}", taskId);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
AiOrder order = aiOrderService.selectOneByThirdPartyOrderNum(taskId);
|
// 锁内二次查询,防止并发时状态变更
|
||||||
|
order = aiOrderService.selectOneByThirdPartyOrderNum(taskId);
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
// 可能是其他环境生成的但回调地址配置成正式的
|
// 可能是其他环境生成的但回调地址配置成正式的
|
||||||
logger.warn("volcCallback aiorder is not exist! third party order num = {}", taskId);
|
logger.warn("volcCallback aiorder is not exist! third party order num = {}", taskId);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
// 5、状态为队列中、执行中,只更新任务状态
|
// 6、状态为队列中、执行中,只更新任务状态
|
||||||
result = volcCallbackRunningTaskProcess(taskResp, order);
|
result = volcCallbackRunningTaskProcess(taskResp, order);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 6、订单数据校验
|
// 7、订单数据校验
|
||||||
result = volcCallbackOrderCheck(taskResp, order);
|
result = volcCallbackOrderCheck(taskResp, order);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// 7、根据状态做不同的处理(加事务)
|
// 8、根据状态做不同的处理(加事务)
|
||||||
String status = taskResp.getStatus();
|
String status = taskResp.getStatus().toLowerCase();
|
||||||
if (VideoTaskStatusType.SUCCEEDED.getName().equals(status)) {
|
if (VideoTaskStatusType.SUCCEEDED.getName().equals(status)) {
|
||||||
// 成功,预扣
|
// 成功,预扣
|
||||||
return volcCallbackSuccessProcess(request, taskResp, order);
|
return aiOrderService.volcCallbackSuccessProcess(request, taskResp, order);
|
||||||
} else {
|
} else {
|
||||||
// 前面已判断过status的合法性,并处理了三种非失败的状态,所以可以确定是取消、失败、超时
|
// 前面已判断过status的合法性,并处理了三种非失败的状态,所以可以确定是取消、失败、超时
|
||||||
return volcCallbackFailProcess(taskResp, order);
|
aiOrderService.orderFailure(order);
|
||||||
|
return AjaxResult.success();
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
|
@ -405,55 +415,6 @@ public class ByteApiController extends BaseController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
private AjaxResult volcCallbackFailProcess(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
|
||||||
aiOrderService.orderFailure(order);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
private 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();
|
|
||||||
if (requestTotalTokens.equals(officialTotalTokens)) {
|
|
||||||
logger.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) {
|
|
||||||
// 异常情况,应该不会出现,以防万一
|
|
||||||
logger.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 (order.getPreDeductAmount() != null
|
|
||||||
&& order.getPreDeductAmount().compareTo(new BigDecimal(0)) > 0) {
|
|
||||||
BigDecimal addAmount = order.getPreDeductAmount().subtract(realAmount);
|
|
||||||
if (addAmount.compareTo(new BigDecimal(0)) != 0) {
|
|
||||||
// 回补
|
|
||||||
aiUserService.addUserBalance(order.getOrderNum(), order.getUserId(), addAmount,
|
|
||||||
BalanceChangerConstants.REFUND, TASK_SUCCESS_BALANCE_REMARK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 设置视频地址与状态
|
|
||||||
if (taskResp.getContent() != null && StringUtils.isNotEmpty(taskResp.getContent().getVideoUrl())) {
|
|
||||||
order.setResult(taskResp.getContent().getVideoUrl());
|
|
||||||
}
|
|
||||||
// 设置用量
|
|
||||||
order.setAmount(realAmount);
|
|
||||||
// 订单状态
|
|
||||||
order.setStatus(AiOrderStatusType.FINISH.ordinal());
|
|
||||||
order.setIsBackfilled(1);
|
|
||||||
aiOrderService.orderSuccess(order);
|
|
||||||
return AjaxResult.success("callback success");
|
|
||||||
}
|
|
||||||
|
|
||||||
private AjaxResult volcCallbackOrderCheck(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
private AjaxResult volcCallbackOrderCheck(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
||||||
// 订单状态如果不为执行中则不做处理
|
// 订单状态如果不为执行中则不做处理
|
||||||
if (order.getStatus() != null && order.getStatus() != AiOrderStatusType.RUNNING.ordinal()) {
|
if (order.getStatus() != null && order.getStatus() != AiOrderStatusType.RUNNING.ordinal()) {
|
||||||
|
|
@ -471,7 +432,7 @@ public class ByteApiController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private AjaxResult volcCallbackByteCheck(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp) {
|
private AjaxResult volcCallbackByteCheck(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp) {
|
||||||
String status = taskResp.getStatus();
|
String status = request.getStatus().toLowerCase();
|
||||||
// 请求的状态与字节的状态是否一致
|
// 请求的状态与字节的状态是否一致
|
||||||
if (!status.equals(taskResp.getStatus().toLowerCase())) {
|
if (!status.equals(taskResp.getStatus().toLowerCase())) {
|
||||||
logger.error("volcCallback request's status != official status! order third party order num = {}, request's status = {}, official status = {}",
|
logger.error("volcCallback request's status != official status! order third party order num = {}, request's status = {}, official status = {}",
|
||||||
|
|
@ -501,7 +462,7 @@ public class ByteApiController extends BaseController {
|
||||||
private AjaxResult volcCallbackRunningTaskProcess(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
private AjaxResult volcCallbackRunningTaskProcess(GetVideoGenerationTaskResponse taskResp, AiOrder order) {
|
||||||
// 执行中状态 ,更新到ext_status字段
|
// 执行中状态 ,更新到ext_status字段
|
||||||
Integer extStatus = null;
|
Integer extStatus = null;
|
||||||
String status = taskResp.getStatus();
|
String status = taskResp.getStatus().toLowerCase();
|
||||||
if (VideoTaskStatusType.QUEUED.getName().equals(status)) {
|
if (VideoTaskStatusType.QUEUED.getName().equals(status)) {
|
||||||
extStatus = 0;
|
extStatus = 0;
|
||||||
} else if (VideoTaskStatusType.RUNNING.getName().equals(status)) {
|
} else if (VideoTaskStatusType.RUNNING.getName().equals(status)) {
|
||||||
|
|
@ -534,33 +495,33 @@ public class ByteApiController extends BaseController {
|
||||||
// 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("取消视频生成任务")
|
||||||
|
|
|
||||||
|
|
@ -272,19 +272,20 @@ public class PortalVideoController extends BaseController {
|
||||||
|
|
||||||
String key = apiKey();
|
String key = apiKey();
|
||||||
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
|
ByteBodyRes byteBodyRes = byteService.imgToVideo(byteBodyReq, key);
|
||||||
String id = byteBodyRes.getId();
|
String thirdPartyOrderNumId = byteBodyRes.getId();
|
||||||
if (id == null) {
|
if (thirdPartyOrderNumId == 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, id);
|
mergeVolcTaskIdIntoVideoParams(aiOrder, thirdPartyOrderNumId);
|
||||||
aiOrder.setResult(id);
|
aiOrder.setResult(thirdPartyOrderNumId);
|
||||||
// 字节订单号与请求ID
|
// 字节订单号与请求ID
|
||||||
aiOrder.setThirdPartyOrderNum(id);
|
aiOrder.setThirdPartyOrderNum(thirdPartyOrderNumId);
|
||||||
aiOrder.setVideoGenRequestId(byteBodyRes.getRequestId());
|
aiOrder.setVideoGenRequestId(byteBodyRes.getRequestId());
|
||||||
aiOrderService.orderSuccess(aiOrder);
|
// aiOrderService.orderSuccess(aiOrder);
|
||||||
|
aiOrderService.updateAiOrder(aiOrder);
|
||||||
// 查询任务详情,按字节返回的预扣数量扣减
|
// 查询任务详情,按字节返回的预扣数量扣减
|
||||||
GetVideoGenerationTaskResponse task = byteService.getVideoGenerationTasks(id, key);
|
GetVideoGenerationTaskResponse task = byteService.getVideoGenerationTasks(thirdPartyOrderNumId, key);
|
||||||
if (task == null || task.getUsage() == null || task.getUsage().getTotalTokens() == null) {
|
if (task == null || task.getUsage() == null || task.getUsage().getTotalTokens() == null) {
|
||||||
return AjaxResult.error(-2, "generation failed, byte task's usage is null");
|
return AjaxResult.error(-2, "generation failed, byte task's usage is null");
|
||||||
}
|
}
|
||||||
|
|
@ -292,16 +293,21 @@ public class PortalVideoController extends BaseController {
|
||||||
return AjaxResult.error(-2, "generation failed, byte task's totalTokens <= 0");
|
return AjaxResult.error(-2, "generation failed, byte task's totalTokens <= 0");
|
||||||
}
|
}
|
||||||
BigDecimal totalTokens = new BigDecimal(task.getUsage().getTotalTokens());
|
BigDecimal totalTokens = new BigDecimal(task.getUsage().getTotalTokens());
|
||||||
// 扣减余额
|
// 同步设置aiOrder,以防在抛异常时数值没变
|
||||||
aiUserService.addUserBalance(aiOrder.getOrderNum(), SecurityUtils.getAiUserId()
|
aiOrder.setPreDeductAmount(totalTokens);
|
||||||
, NumberUtil.mul(-1, totalTokens), aiOrderService.getChangerType(functionType));
|
aiOrder.setAmount(totalTokens);
|
||||||
|
|
||||||
// 设置订单信息
|
// 设置订单信息
|
||||||
AiOrder updAiOrder = new AiOrder();
|
AiOrder updAiOrder = new AiOrder();
|
||||||
|
updAiOrder.setId(aiOrder.getId());
|
||||||
updAiOrder.setOrderNum(aiOrder.getOrderNum());
|
updAiOrder.setOrderNum(aiOrder.getOrderNum());
|
||||||
updAiOrder.setPreDeductAmount(totalTokens);
|
updAiOrder.setPreDeductAmount(totalTokens);
|
||||||
// 先设置成预扣数量,等收到回调再改过来,这样后续报表会比较准确
|
// 先设置成预扣数量,等收到回调再改过来,这样后续报表会比较准确
|
||||||
updAiOrder.setAmount(totalTokens);
|
updAiOrder.setAmount(totalTokens);
|
||||||
aiOrderService.updateAiOrder(updAiOrder);
|
aiOrderService.updateAiOrder(updAiOrder);
|
||||||
|
// 扣减余额
|
||||||
|
aiUserService.addUserBalance(aiOrder.getOrderNum(), SecurityUtils.getAiUserId()
|
||||||
|
, NumberUtil.mul(-1, totalTokens), aiOrderService.getChangerType(functionType));
|
||||||
return AjaxResult.success(byteBodyRes);
|
return AjaxResult.success(byteBodyRes);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
aiOrderService.orderFailure(aiOrder);
|
aiOrderService.orderFailure(aiOrder);
|
||||||
|
|
@ -626,19 +632,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;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,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
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ 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;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,4 +95,9 @@ public interface IAiOrderService {
|
||||||
BigDecimal getSumAmountByUserId(String userId);
|
BigDecimal getSumAmountByUserId(String userId);
|
||||||
|
|
||||||
AiOrder selectOneByThirdPartyOrderNum(String id);
|
AiOrder selectOneByThirdPartyOrderNum(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 火山回调 - 任务成功时处理流程
|
||||||
|
*/
|
||||||
|
AjaxResult volcCallbackSuccessProcess(VideoTaskCallBackRequest request, GetVideoGenerationTaskResponse taskResp, AiOrder order);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
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;
|
import com.ruoyi.common.core.response.video.GetVideoGenerationTaskResponse;
|
||||||
|
|
||||||
public interface IByteService {
|
public interface IByteService {
|
||||||
|
|
@ -55,5 +57,5 @@ public interface IByteService {
|
||||||
/**
|
/**
|
||||||
* GET 查询视频生成任务(单个)
|
* GET 查询视频生成任务(单个)
|
||||||
*/
|
*/
|
||||||
GetVideoGenerationTaskResponse getVideoGenerationTasks(String id, String arkApiKey) throws Exception;
|
GetVideoGenerationTaskResponse getVideoGenerationTasks(String thirdPartyOrderNumId, String arkApiKey) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,17 @@ 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;
|
||||||
|
|
@ -36,6 +42,7 @@ 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 {
|
||||||
|
|
||||||
|
|
@ -51,6 +58,8 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IAiStatisticsService aiStatisticsService;
|
private IAiStatisticsService aiStatisticsService;
|
||||||
|
|
||||||
|
// 任务成功时流水表备注
|
||||||
|
private static final String TASK_SUCCESS_BALANCE_REMARK = "order.number.generation.successes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询订单管理
|
* 查询订单管理
|
||||||
|
|
@ -266,4 +275,53 @@ public class AiOrderServiceImpl implements IAiOrderService {
|
||||||
public AiOrder selectOneByThirdPartyOrderNum(String id) {
|
public AiOrder selectOneByThirdPartyOrderNum(String id) {
|
||||||
return aiOrderMapper.selectOneByThirdPartyOrderNum(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);
|
||||||
|
// 订单状态
|
||||||
|
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) {
|
||||||
|
// 回补
|
||||||
|
aiUserService.addUserBalance(order.getOrderNum(), order.getUserId(), addAmount,
|
||||||
|
BalanceChangerConstants.REFUND, TASK_SUCCESS_BALANCE_REMARK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AjaxResult.success("callback success");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,17 @@ 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.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;
|
||||||
|
|
@ -18,6 +23,9 @@ 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
|
||||||
|
|
@ -241,15 +249,15 @@ public class ByteService implements IByteService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GetVideoGenerationTaskResponse getVideoGenerationTasks(String id, String arkApiKey) throws Exception {
|
public GetVideoGenerationTaskResponse getVideoGenerationTasks(String thirdPartyOrderNumId, String arkApiKey) throws Exception {
|
||||||
if (StringUtils.isBlank(arkApiKey)) {
|
if (StringUtils.isBlank(arkApiKey)) {
|
||||||
throw new Exception("getVideoGenerationTasks error:apiKey is null");
|
throw new Exception("getVideoGenerationTasks error:apiKey is null");
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(id)) {
|
if (StringUtils.isBlank(thirdPartyOrderNumId)) {
|
||||||
throw new Exception("getVideoGenerationTasks error:id is null");
|
throw new Exception("getVideoGenerationTasks error:thirdPartyOrderNumId is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpUrl parsed = HttpUrl.parse(volcBaseUrl + "/api/v3/contents/generations/tasks/" + id);
|
HttpUrl parsed = HttpUrl.parse(volcBaseUrl + "/api/v3/contents/generations/tasks/" + thirdPartyOrderNumId);
|
||||||
if (parsed == null) {
|
if (parsed == null) {
|
||||||
throw new Exception("listVideoGenerationTasks error:invalid base url");
|
throw new Exception("listVideoGenerationTasks error:invalid base url");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue