feat: 优化

This commit is contained in:
2026-02-26 18:52:09 +08:00
parent c2e4fde218
commit b76e3ff47d
17 changed files with 1027 additions and 1630 deletions

View File

@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.tik.muye.pointrecord;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.tik.muye.pointrecord.dal.PointRecordDO;
import cn.iocoder.yudao.module.tik.muye.pointrecord.service.PointRecordService;
import cn.iocoder.yudao.module.tik.muye.pointrecord.vo.PointRecordPageReqVO;
import cn.iocoder.yudao.module.tik.muye.pointrecord.vo.PointRecordRespVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.object.BeanUtils.toBean;
/**
* 用户 App - 积分记录
*/
@Tag(name = "用户 App - 积分记录")
@RestController
@RequestMapping("/api/tik/point-record")
@Validated
public class AppPointRecordController {
@Resource
private PointRecordService pointRecordService;
@GetMapping("/page")
@Operation(summary = "获取当前用户积分记录分页")
public CommonResult<PageResult<PointRecordRespVO>> getPointRecordPage(@Valid PointRecordPageReqVO pageReqVO) {
// 强制使用当前登录用户ID防止查询其他用户数据
pageReqVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
PageResult<PointRecordDO> pageResult = pointRecordService.getPointRecordPage(pageReqVO);
return success(toBean(pageResult, PointRecordRespVO.class));
}
}

View File

@@ -52,6 +52,10 @@ public class PointRecordRespVO {
@ExcelProperty("备注")
private String remark;
@Schema(description = "状态pending-预扣 confirmed-已确认 canceled-已取消", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("状态")
private String status;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;

View File

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.tik.voice.vo.AppTikLatentsyncResultRespVO;
import cn.iocoder.yudao.module.tik.kling.service.KlingService;
import cn.iocoder.yudao.module.tik.kling.dto.KlingLipSyncQueryResponse;
import cn.iocoder.yudao.module.tik.kling.vo.response.KlingLipSyncVideoVO;
import cn.iocoder.yudao.module.tik.muye.points.service.PointsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -35,6 +36,7 @@ public class LatentsyncPollingService {
private final LatentsyncService latentsyncService;
private final StringRedisTemplate stringRedisTemplate;
private final KlingService klingService;
private final PointsService pointsService;
// ========== 常量 ==========
private static final String REDIS_POLLING_PREFIX = "latentsync:polling:";
@@ -218,6 +220,10 @@ public class LatentsyncPollingService {
@Transactional(rollbackFor = Exception.class)
private void completeTask(Long taskId, String videoUrl, String requestId) {
try {
// 1. 先查询任务获取预扣记录ID
TikDigitalHumanTaskDO task = TenantUtils.executeIgnore(() -> taskMapper.selectById(taskId));
// 2. 更新任务状态
TikDigitalHumanTaskDO updateObj = new TikDigitalHumanTaskDO();
updateObj.setId(taskId);
updateObj.setStatus("SUCCESS");
@@ -228,7 +234,17 @@ public class LatentsyncPollingService {
TenantUtils.executeIgnore(() -> taskMapper.updateById(updateObj));
// 缓存结果
// 3. 确认预扣(任务成功,实际扣费)
if (task != null && task.getPendingRecordId() != null) {
try {
pointsService.confirmPendingDeduct(task.getPendingRecordId());
log.info("[completeTask][任务({})成功确认扣费预扣记录ID({})]", taskId, task.getPendingRecordId());
} catch (Exception e) {
log.error("[completeTask][确认扣费失败taskId={}recordId={}]", taskId, task.getPendingRecordId(), e);
}
}
// 4. 缓存结果
String resultKey = REDIS_RESULT_PREFIX + taskId;
stringRedisTemplate.opsForValue().set(resultKey, videoUrl, RESULT_CACHE_TIME);
@@ -248,6 +264,10 @@ public class LatentsyncPollingService {
@Transactional(rollbackFor = Exception.class)
private void markTaskFailed(Long taskId, String errorMessage) {
try {
// 1. 先查询任务获取预扣记录ID
TikDigitalHumanTaskDO task = TenantUtils.executeIgnore(() -> taskMapper.selectById(taskId));
// 2. 更新任务状态
TikDigitalHumanTaskDO updateObj = new TikDigitalHumanTaskDO();
updateObj.setId(taskId);
updateObj.setStatus("FAILED");
@@ -256,6 +276,16 @@ public class LatentsyncPollingService {
TenantUtils.executeIgnore(() -> taskMapper.updateById(updateObj));
// 3. 取消预扣(任务失败,不扣费)
if (task != null && task.getPendingRecordId() != null) {
try {
pointsService.cancelPendingDeduct(task.getPendingRecordId());
log.info("[markTaskFailed][任务({})失败取消预扣预扣记录ID({})]", taskId, task.getPendingRecordId());
} catch (Exception e) {
log.error("[markTaskFailed][取消预扣失败taskId={}recordId={}]", taskId, task.getPendingRecordId(), e);
}
}
log.warn("[markTaskFailed][任务失败][taskId={}, error={}]", taskId, errorMessage);
} catch (Exception e) {
log.error("[markTaskFailed][标记任务失败失败][taskId={}]", taskId, e);
@@ -266,6 +296,10 @@ public class LatentsyncPollingService {
* 更新任务状态
*/
private void updateTaskStatus(Long taskId, String status, String currentStep, Integer progress, String errorMessage) {
// 1. 先查询任务获取预扣记录ID
TikDigitalHumanTaskDO task = TenantUtils.executeIgnore(() -> taskMapper.selectById(taskId));
// 2. 更新任务状态
TikDigitalHumanTaskDO updateObj = new TikDigitalHumanTaskDO();
updateObj.setId(taskId);
updateObj.setStatus(status);
@@ -274,11 +308,29 @@ public class LatentsyncPollingService {
if ("SUCCESS".equals(status)) {
updateObj.setFinishTime(LocalDateTime.now());
// 确认预扣(任务成功)
if (task != null && task.getPendingRecordId() != null) {
try {
pointsService.confirmPendingDeduct(task.getPendingRecordId());
log.info("[updateTaskStatus][任务({})成功,确认扣费]", taskId);
} catch (Exception e) {
log.error("[updateTaskStatus][确认扣费失败taskId={}]", taskId, e);
}
}
} else if ("PROCESSING".equals(status)) {
updateObj.setStartTime(LocalDateTime.now());
} else if ("FAILED".equals(status)) {
updateObj.setErrorMessage(errorMessage);
updateObj.setFinishTime(LocalDateTime.now());
// 取消预扣(任务失败)
if (task != null && task.getPendingRecordId() != null) {
try {
pointsService.cancelPendingDeduct(task.getPendingRecordId());
log.info("[updateTaskStatus][任务({})失败,取消预扣]", taskId);
} catch (Exception e) {
log.error("[updateTaskStatus][取消预扣失败taskId={}]", taskId, e);
}
}
}
TenantUtils.executeIgnore(() -> taskMapper.updateById(updateObj));

View File

@@ -391,10 +391,11 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
transcriptionText = reqVO.getTranscriptionText();
}
String finalText = determineSynthesisText(
transcriptionText,
reqVO.getInputText(),
false);
// transcriptionText 仅用于提高克隆质量,不拼接到合成文本
String finalText = reqVO.getInputText();
if (StrUtil.isBlank(finalText)) {
throw exception(VOICE_TTS_FAILED, "请提供需要合成的文本内容");
}
String cacheKey = buildCacheKey(SYNTH_CACHE_PREFIX,
voiceId,
@@ -444,7 +445,7 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
log.error("[synthesizeVoice][积分扣减失败: {}]", e.getMessage());
}
// 【安全方案】不暴露OSS链接直接返回Base64编码的音频数据
// 不暴露OSS链接直接返回Base64编码的音频数据
String audioBase64 = Base64.getEncoder().encodeToString(ttsResult.getAudio());
log.info("[synthesizeVoice][合成成功,配音编号({})voiceId({})format({})audioSize={}]",
voiceConfigId, finalVoiceId, format, ttsResult.getAudio().length);
@@ -617,9 +618,6 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
/**
* 从URL中提取原始URL去除查询参数和锚点
*
* @param url 可能包含查询参数的URL
* @return 原始URL去除查询参数和锚点
*/
private String extractRawUrl(String url) {
if (StrUtil.isBlank(url)) {
@@ -627,10 +625,8 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
}
try {
java.net.URL urlObj = new java.net.URL(url);
// 只使用协议、主机、路径部分,忽略查询参数和锚点
return urlObj.getProtocol() + "://" + urlObj.getHost() + urlObj.getPath();
} catch (Exception e) {
// 如果URL解析失败使用简单方式去除查询参数
return url.split("\\?")[0].split("#")[0];
}
}
@@ -644,18 +640,15 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
String instruction,
String audioFormat,
Integer sampleRate) {
// 构建标识符优先使用voiceId如果没有则使用fileUrl的稳定部分去除查询参数
String identifier;
if (StrUtil.isNotBlank(voiceId)) {
identifier = voiceId;
} else if (StrUtil.isNotBlank(fileUrl)) {
// 对于fileUrl提取稳定部分去除预签名URL的查询参数避免缓存key不稳定
identifier = extractRawUrl(fileUrl);
} else {
identifier = "no-voice";
}
// 获取默认配置
String defaultFormat = getDefaultFormat();
Integer defaultSampleRate = getDefaultSampleRate();
@@ -667,8 +660,7 @@ public class TikUserVoiceServiceImpl implements TikUserVoiceService {
instruction,
StrUtil.blankToDefault(audioFormat, defaultFormat),
sampleRate != null ? sampleRate : defaultSampleRate);
String hash = cn.hutool.crypto.SecureUtil.sha256(payload);
return prefix + hash;
return prefix + cn.hutool.crypto.SecureUtil.sha256(payload);
}
private PreviewCacheEntry getPreviewCache(String key) {