优化
This commit is contained in:
@@ -30,11 +30,11 @@
|
||||
</div>
|
||||
|
||||
<!-- APlayer 播放器容器 -->
|
||||
<div v-if="audioUrl" ref="playerContainer" class="aplayer-container"></div>
|
||||
<div v-show="audioUrl" ref="playerContainer" class="aplayer-container"></div>
|
||||
|
||||
<!-- 下载按钮 -->
|
||||
<a-button
|
||||
v-if="audioUrl"
|
||||
v-show="audioUrl"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="downloadAudio"
|
||||
@@ -47,8 +47,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Empty } from 'ant-design-vue'
|
||||
import { SoundOutlined } from '@ant-design/icons-vue'
|
||||
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
||||
import { useTTS, TTS_PROVIDERS } from '@/composables/useTTS'
|
||||
import APlayer from 'aplayer'
|
||||
@@ -64,13 +64,8 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||
|
||||
const goToVoiceManage = () => {
|
||||
router.push('/digital-human/voice-copy')
|
||||
}
|
||||
|
||||
const voiceStore = useVoiceCopyStore()
|
||||
|
||||
const emit = defineEmits(['select'])
|
||||
@@ -205,14 +200,12 @@ const initPlayer = (url) => {
|
||||
audioUrl.value = url
|
||||
|
||||
nextTick(() => {
|
||||
if (!playerContainer.value) return
|
||||
|
||||
player = new APlayer({
|
||||
container: playerContainer.value,
|
||||
autoplay: true,
|
||||
theme: '#3b82f6',
|
||||
preload: 'auto',
|
||||
volume: 0.7,
|
||||
loop: 'none',
|
||||
audio: [{
|
||||
name: currentVoiceName.value || '语音合成',
|
||||
artist: '合成',
|
||||
@@ -221,7 +214,9 @@ const initPlayer = (url) => {
|
||||
}]
|
||||
})
|
||||
|
||||
// 播放结束后保留播放器,让用户可以再次播放
|
||||
player.on('ended', () => {
|
||||
player.seek(0)
|
||||
})
|
||||
|
||||
player.on('error', (e) => {
|
||||
console.error('APlayer 播放错误:', e)
|
||||
@@ -235,9 +230,10 @@ const initPlayer = (url) => {
|
||||
const downloadAudio = () => {
|
||||
if (!audioUrl.value) return
|
||||
|
||||
const filename = `${currentVoiceName.value || '语音合成'}.mp3`
|
||||
const link = document.createElement('a')
|
||||
link.href = audioUrl.value
|
||||
link.download = `${currentVoiceName.value || '语音合成'}.mp3`
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
@@ -255,10 +251,11 @@ const destroyPlayer = () => {
|
||||
}
|
||||
player = null
|
||||
}
|
||||
if (audioUrl.value) {
|
||||
// 只对 blob URL 调用 revokeObjectURL
|
||||
if (audioUrl.value && audioUrl.value.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(audioUrl.value)
|
||||
audioUrl.value = ''
|
||||
}
|
||||
audioUrl.value = ''
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
@@ -29,16 +29,15 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@Slf4j
|
||||
@@ -50,6 +49,12 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
private static final String PLATFORM_TIKHUB = "tikhub";
|
||||
private static final String MODEL_CODE_CRAWLER = "crawler";
|
||||
|
||||
// 日志前缀
|
||||
private static final String LOG_PREFIX_TIKHUP = "[postTikHup]";
|
||||
private static final String LOG_PREFIX_VIDEO = "[videoToCharacters]";
|
||||
private static final String LOG_PREFIX_VIDEO2 = "[videoToCharacters2]";
|
||||
private static final String LOG_PREFIX_DEEPSEEK = "[deepseekAnalysis]";
|
||||
|
||||
// 缓存配置
|
||||
private static final String CACHE_KEY_PREFIX = "tikhub:cache:";
|
||||
private static final long CACHE_EXPIRE_HOURS = 24;
|
||||
@@ -78,11 +83,11 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
public Object postTikHup(String type, String methodType, String urlParams, String paramType) {
|
||||
// 1. 参数校验
|
||||
if (StringUtils.isBlank(type)) {
|
||||
log.error("postTikHup: type 参数为空");
|
||||
log.error("{} type 参数为空", LOG_PREFIX_TIKHUP);
|
||||
return CommonResult.error(400, "接口类型不能为空");
|
||||
}
|
||||
if (StringUtils.isBlank(methodType)) {
|
||||
log.error("postTikHup: methodType 参数为空");
|
||||
log.error("{} methodType 参数为空", LOG_PREFIX_TIKHUP);
|
||||
return CommonResult.error(400, "请求方法类型不能为空");
|
||||
}
|
||||
|
||||
@@ -94,55 +99,62 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_CODE_CRAWLER);
|
||||
pointsService.checkPoints(userId, config.getConsumePoints());
|
||||
} catch (Exception e) {
|
||||
log.error("[postTikHup] 积分预检失败: {}", e.getMessage());
|
||||
log.error("{} 积分预检失败: {}", LOG_PREFIX_TIKHUP, e.getMessage());
|
||||
return CommonResult.error(400, e.getMessage());
|
||||
}
|
||||
|
||||
// 3. 尝试从缓存获取结果(缓存命中也要扣积分)
|
||||
// 3. 尝试从缓存获取结果
|
||||
String cacheKey = buildCacheKey(type, methodType, urlParams, paramType);
|
||||
String cachedResult = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
if (StringUtils.isNotBlank(cachedResult)) {
|
||||
log.info("[postTikHup] 命中缓存, cacheKey: {}", cacheKey);
|
||||
// 缓存命中也扣积分
|
||||
deductPointsSafely(userId, config.getConsumePoints(), type);
|
||||
String cachedUrl = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
if (StringUtils.isNotBlank(cachedUrl)) {
|
||||
log.info("{} 命中缓存 URL, cacheKey: {}, cacheUrl: {}", LOG_PREFIX_TIKHUP, cacheKey, cachedUrl);
|
||||
try {
|
||||
return JSON.parseObject(cachedResult);
|
||||
Unirest.setTimeouts(0, 0);
|
||||
HttpResponse<String> cachedResponse = Unirest.get(cachedUrl)
|
||||
.header("Authorization", "Bearer " + config.getApiKey())
|
||||
.asString();
|
||||
|
||||
if (cachedResponse.getStatus() == 200 && StringUtils.isNotBlank(cachedResponse.getBody())) {
|
||||
deductPointsSafely(userId, config.getConsumePoints(), type);
|
||||
return JSON.parseObject(cachedResponse.getBody()).get("data");
|
||||
}
|
||||
log.warn("{} 缓存 URL 请求失败, 状态码: {}, 清除缓存", LOG_PREFIX_TIKHUP, cachedResponse.getStatus());
|
||||
stringRedisTemplate.delete(cacheKey);
|
||||
} catch (Exception e) {
|
||||
return cachedResult;
|
||||
log.warn("{} 缓存 URL 请求异常: {}, 清除缓存", LOG_PREFIX_TIKHUP, e.getMessage());
|
||||
stringRedisTemplate.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 从 muye_ai_model_config 获取 apiKey
|
||||
// 4. 获取 apiKey
|
||||
String authorization = config.getApiKey();
|
||||
if (StringUtils.isBlank(authorization)) {
|
||||
log.error("postTikHup: TikHub 配置的 apiKey 为空");
|
||||
log.error("{} TikHub 配置的 apiKey 为空", LOG_PREFIX_TIKHUP);
|
||||
return CommonResult.error(500, "接口配置错误:apiKey 为空");
|
||||
}
|
||||
|
||||
// 5. 获取接口 URL(从 tik_token 表)
|
||||
// 5. 获取接口 URL
|
||||
TikTokenVO tikTokenVO = tikTokenMapper.getInterfaceUrl(type);
|
||||
if (tikTokenVO == null) {
|
||||
log.error("postTikHup: 未找到接口类型 {} 的 URL 配置", type);
|
||||
log.error("{} 未找到接口类型 {} 的 URL 配置", LOG_PREFIX_TIKHUP, type);
|
||||
return CommonResult.error(404, "未找到接口类型 " + type + " URL 配置");
|
||||
}
|
||||
String url = tikTokenVO.getPlatformUrl();
|
||||
if (StringUtils.isBlank(url)) {
|
||||
log.error("postTikHup: 接口类型 {} 的 URL 为空", type);
|
||||
log.error("{} 接口类型 {} 的 URL 为空", LOG_PREFIX_TIKHUP, type);
|
||||
return CommonResult.error(500, "接口配置错误:URL 为空");
|
||||
}
|
||||
|
||||
// 6. 统一转换为小写进行比较
|
||||
// 6. 执行请求
|
||||
String methodTypeLower = methodType.toLowerCase();
|
||||
String paramTypeLower = paramType != null ? paramType.toLowerCase() : "";
|
||||
|
||||
try {
|
||||
Unirest.setTimeouts(0, 0);
|
||||
HttpResponse<String> response = executeRequest(url, urlParams, authorization, methodTypeLower, paramTypeLower);
|
||||
|
||||
// 7. 处理响应并缓存
|
||||
return handleResponse(response, url, userId, config.getConsumePoints(), type, cacheKey);
|
||||
} catch (Exception e) {
|
||||
log.error("postTikHup: 接口调用异常, URL: {}, 错误信息: {}", url, e.getMessage(), e);
|
||||
log.error("{} 接口调用异常, URL: {}, 错误信息: {}", LOG_PREFIX_TIKHUP, url, e.getMessage(), e);
|
||||
return CommonResult.error(500, "接口调用异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -150,24 +162,24 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
private HttpResponse<String> executeRequest(String url, String urlParams, String authorization,
|
||||
String methodTypeLower, String paramTypeLower) throws Exception {
|
||||
if ("post".equals(methodTypeLower) && "json".equals(paramTypeLower)) {
|
||||
log.debug("postTikHup: POST JSON 请求, URL: {}, Body: {}", url, urlParams);
|
||||
log.debug("{} POST JSON 请求, URL: {}, Body: {}", LOG_PREFIX_TIKHUP, url, urlParams);
|
||||
return Unirest.post(url)
|
||||
.header("Authorization", "Bearer " + authorization)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(urlParams)
|
||||
.asString();
|
||||
} else if ("post".equals(methodTypeLower)) {
|
||||
log.debug("postTikHup: POST 表单请求, URL: {}?{}", url, urlParams);
|
||||
}
|
||||
if ("post".equals(methodTypeLower)) {
|
||||
log.debug("{} POST 表单请求, URL: {}?{}", LOG_PREFIX_TIKHUP, url, urlParams);
|
||||
return Unirest.post(url + "?" + urlParams)
|
||||
.header("Authorization", "Bearer " + authorization)
|
||||
.asString();
|
||||
} else {
|
||||
String finalUrl = buildUrlWithParams(url, urlParams);
|
||||
log.info("postTikHup: GET 请求, 最终URL: {}", finalUrl);
|
||||
return Unirest.get(finalUrl)
|
||||
.header("Authorization", "Bearer " + authorization)
|
||||
.asString();
|
||||
}
|
||||
String finalUrl = buildUrlWithParams(url, urlParams);
|
||||
log.info("{} GET 请求, 最终URL: {}", LOG_PREFIX_TIKHUP, finalUrl);
|
||||
return Unirest.get(finalUrl)
|
||||
.header("Authorization", "Bearer " + authorization)
|
||||
.asString();
|
||||
}
|
||||
|
||||
private String buildUrlWithParams(String url, String urlParams) {
|
||||
@@ -179,41 +191,53 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
|
||||
private Object handleResponse(HttpResponse<String> response, String url, String userId,
|
||||
Integer consumePoints, String type, String cacheKey) {
|
||||
int statusCode = response.getStatus();
|
||||
int httpStatus = response.getStatus();
|
||||
String responseBody = response.getBody();
|
||||
|
||||
if (statusCode != 200) {
|
||||
log.error("postTikHup: 接口调用失败, URL: {}, 状态码: {}, 响应: {}", url, statusCode, responseBody);
|
||||
return CommonResult.error(statusCode,
|
||||
"接口调用失败: " + (StringUtils.isNotBlank(responseBody) ? responseBody : "HTTP " + statusCode));
|
||||
if (httpStatus != 200) {
|
||||
log.error("{} HTTP 请求失败, URL: {}, 状态码: {}, 响应: {}", LOG_PREFIX_TIKHUP, url, httpStatus, responseBody);
|
||||
return CommonResult.error(httpStatus,
|
||||
"HTTP 请求失败: " + (StringUtils.isNotBlank(responseBody) ? responseBody : "HTTP " + httpStatus));
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(responseBody)) {
|
||||
log.warn("postTikHup: 接口返回空响应, URL: {}", url);
|
||||
log.warn("{} 接口返回空响应, URL: {}", LOG_PREFIX_TIKHUP, url);
|
||||
return CommonResult.error(500, "接口返回空响应");
|
||||
}
|
||||
|
||||
// 扣减积分
|
||||
com.alibaba.fastjson.JSONObject respJson;
|
||||
try {
|
||||
respJson = JSON.parseObject(responseBody);
|
||||
} catch (Exception e) {
|
||||
log.error("{} 响应体 JSON 解析失败, URL: {}, 响应: {}", LOG_PREFIX_TIKHUP, url, responseBody);
|
||||
return CommonResult.error(500, "响应体解析失败");
|
||||
}
|
||||
|
||||
Integer bizCode = respJson.getInteger("code");
|
||||
String bizMsg = respJson.getString("message");
|
||||
if (bizCode == null || bizCode != 200) {
|
||||
log.error("{} 业务调用失败, URL: {}, code: {}, message: {}", LOG_PREFIX_TIKHUP, url, bizCode, bizMsg);
|
||||
return CommonResult.error(bizCode != null ? bizCode : 500,
|
||||
"业务调用失败: " + (StringUtils.isNotBlank(bizMsg) ? bizMsg : "code=" + bizCode));
|
||||
}
|
||||
|
||||
deductPointsSafely(userId, consumePoints, type);
|
||||
|
||||
// 缓存成功响应(24小时)
|
||||
stringRedisTemplate.opsForValue().set(cacheKey, responseBody, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
||||
log.info("[postTikHup] 结果已缓存, cacheKey: {}, 有效期: {}小时", cacheKey, CACHE_EXPIRE_HOURS);
|
||||
|
||||
// 尝试解析 JSON,失败则返回原始字符串
|
||||
try {
|
||||
return JSON.parseObject(responseBody);
|
||||
} catch (Exception e) {
|
||||
return responseBody;
|
||||
String cacheUrl = respJson.getString("cache_url");
|
||||
if (StringUtils.isNotBlank(cacheUrl)) {
|
||||
stringRedisTemplate.opsForValue().set(cacheKey, cacheUrl, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
|
||||
log.info("{} 缓存 URL 已保存, cacheKey: {}, cacheUrl: {}", LOG_PREFIX_TIKHUP, cacheKey, cacheUrl);
|
||||
}
|
||||
|
||||
return respJson;
|
||||
}
|
||||
|
||||
private void deductPointsSafely(String userId, Integer consumePoints, String type) {
|
||||
try {
|
||||
pointsService.deductPoints(userId, consumePoints, "tikhub_fetch", type);
|
||||
log.info("[postTikHup] 用户 {} 扣减 {} 积分", userId, consumePoints);
|
||||
log.info("{} 用户 {} 扣减 {} 积分", LOG_PREFIX_TIKHUP, userId, consumePoints);
|
||||
} catch (Exception e) {
|
||||
log.error("[postTikHup] 积分扣减失败: {}", e.getMessage());
|
||||
log.error("{} 积分扣减失败: {}", LOG_PREFIX_TIKHUP, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,31 +271,31 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
|
||||
@Override
|
||||
public Object videoToCharacters(String fileLink) {
|
||||
log.info("[videoToCharacters] 开始识别,文件链接: {}", fileLink);
|
||||
log.info("{} 开始识别,文件链接: {}", LOG_PREFIX_VIDEO, fileLink);
|
||||
TikFileTransCharacters transCharacters = new TikFileTransCharacters(accessKeyId, accessKeySecret);
|
||||
String taskId = transCharacters.submitFileTransRequest(appKey, fileLink);
|
||||
|
||||
if (taskId == null) {
|
||||
log.error("[videoToCharacters] 提交识别请求失败,taskId为null,fileLink: {}", fileLink);
|
||||
log.error("{} 提交识别请求失败,taskId为null,fileLink: {}", LOG_PREFIX_VIDEO, fileLink);
|
||||
return CommonResult.error(500, "录音文件识别请求失败!");
|
||||
}
|
||||
|
||||
log.info("[videoToCharacters] 提交识别请求成功,taskId: {}", taskId);
|
||||
log.info("{} 提交识别请求成功,taskId: {}", LOG_PREFIX_VIDEO, taskId);
|
||||
String transResult = transCharacters.getFileTransResult(taskId);
|
||||
|
||||
if (transResult == null) {
|
||||
log.error("[videoToCharacters] 识别结果查询失败,taskId: {}", taskId);
|
||||
log.error("{} 识别结果查询失败,taskId: {}", LOG_PREFIX_VIDEO, taskId);
|
||||
return CommonResult.error(501, "录音文件识别请求失败!");
|
||||
}
|
||||
|
||||
log.info("[videoToCharacters] 识别成功,taskId: {},结果长度: {}", taskId, transResult.length());
|
||||
log.info("{} 识别成功,taskId: {},结果长度: {}", LOG_PREFIX_VIDEO, taskId, transResult.length());
|
||||
return CommonResult.success(transResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object videoToCharacters2(List<String> fileLinkList) {
|
||||
log.info("[videoToCharacters2] 开始识别,文件数量: {},文件URL: {}",
|
||||
fileLinkList != null ? fileLinkList.size() : 0, fileLinkList);
|
||||
log.info("{} 开始识别,文件数量: {},文件URL: {}",
|
||||
LOG_PREFIX_VIDEO2, fileLinkList != null ? fileLinkList.size() : 0, fileLinkList);
|
||||
|
||||
TranscriptionParam param = TranscriptionParam.builder()
|
||||
.apiKey(ALIYUN_API_KEY)
|
||||
@@ -283,16 +307,16 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
try {
|
||||
Transcription transcription = new Transcription();
|
||||
TranscriptionResult result = transcription.asyncCall(param);
|
||||
log.info("[videoToCharacters2] 提交转写请求成功,TaskId: {}", result.getTaskId());
|
||||
log.info("{} 提交转写请求成功,TaskId: {}", LOG_PREFIX_VIDEO2, result.getTaskId());
|
||||
|
||||
result = transcription.wait(TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
|
||||
String outputJson = new GsonBuilder().setPrettyPrinting().create().toJson(result.getOutput());
|
||||
|
||||
log.info("[videoToCharacters2] 识别成功,TaskId: {},结果长度: {}",
|
||||
result.getTaskId(), outputJson != null ? outputJson.length() : 0);
|
||||
log.info("{} 识别成功,TaskId: {},结果长度: {}",
|
||||
LOG_PREFIX_VIDEO2, result.getTaskId(), outputJson != null ? outputJson.length() : 0);
|
||||
return CommonResult.success(outputJson);
|
||||
} catch (Exception e) {
|
||||
log.error("[videoToCharacters2] 识别失败,文件URL: {},异常: {}", fileLinkList, e.getMessage(), e);
|
||||
log.error("{} 识别失败,文件URL: {},异常: {}", LOG_PREFIX_VIDEO2, fileLinkList, e.getMessage(), e);
|
||||
return CommonResult.error(500, "录音文件识别请求失败!");
|
||||
}
|
||||
}
|
||||
@@ -301,7 +325,7 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
public Object deepseekAnalysis(String promptType, String content) {
|
||||
TikPromptVO tikPromptVO = tikPromptMapper.getTikPromptVO(promptType);
|
||||
if (tikPromptVO == null) {
|
||||
log.error("[deepseekAnalysis] 未找到提示词类型: {}", promptType);
|
||||
log.error("{} 未找到提示词类型: {}", LOG_PREFIX_DEEPSEEK, promptType);
|
||||
return CommonResult.error(404, "未找到提示词类型");
|
||||
}
|
||||
|
||||
@@ -317,10 +341,10 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
if (response.getStatus() == 200) {
|
||||
return response.getBody();
|
||||
}
|
||||
log.error("[deepseekAnalysis] 调用失败,状态码: {}", response.getStatus());
|
||||
log.error("{} 调用失败,状态码: {}", LOG_PREFIX_DEEPSEEK, response.getStatus());
|
||||
return CommonResult.error(500, "文案改写失败!");
|
||||
} catch (Exception e) {
|
||||
log.error("[deepseekAnalysis] 异常: {}", e.getMessage(), e);
|
||||
log.error("{} 异常: {}", LOG_PREFIX_DEEPSEEK, e.getMessage(), e);
|
||||
return CommonResult.error(500, "文案改写失败!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user