send-stream
This commit is contained in:
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.tik.chat.vo.app.AppAiChatMessageRespVO;
|
||||
import cn.iocoder.yudao.module.tik.chat.vo.app.AppAiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.tik.chat.vo.app.AppAiChatMessageSendRespVO;
|
||||
import cn.iocoder.yudao.module.tik.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.tik.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.tik.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.tik.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
|
||||
@@ -116,19 +117,8 @@ public class AppAiChatMessageController {
|
||||
|
||||
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
|
||||
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<CommonResult<AppAiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AppAiChatMessageSendReqVO sendReqVO) {
|
||||
// 将 App VO 转换为 Admin VO
|
||||
AiChatMessageSendReqVO adminReqVO = BeanUtils.toBean(sendReqVO, AiChatMessageSendReqVO.class);
|
||||
// 调用 Service,然后转换响应流
|
||||
return chatMessageService.sendChatMessageStream(adminReqVO, getLoginUserId())
|
||||
.map(result -> {
|
||||
if (result.getData() != null) {
|
||||
// 手动转换 segments,因为内部类类型不同
|
||||
AppAiChatMessageSendRespVO appResp = convertSendRespVO(result.getData());
|
||||
return success(appResp);
|
||||
}
|
||||
return success((AppAiChatMessageSendRespVO) null);
|
||||
});
|
||||
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
|
||||
}
|
||||
|
||||
@Operation(summary = "获得指定对话的消息列表")
|
||||
|
||||
@@ -25,4 +25,12 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode FILE_GROUP_NAME_DUPLICATE = new ErrorCode(1_030_000_012, "分组名称重复");
|
||||
ErrorCode FILE_GROUP_NOT_BELONG_TO_USER = new ErrorCode(1_030_000_013, "分组不属于当前用户");
|
||||
|
||||
// ========== 配音管理 1-030-001-000 ==========
|
||||
ErrorCode VOICE_NOT_EXISTS = new ErrorCode(1_030_001_001, "配音不存在");
|
||||
ErrorCode VOICE_NAME_DUPLICATE = new ErrorCode(1_030_001_002, "配音名称重复");
|
||||
ErrorCode VOICE_FILE_NOT_EXISTS = new ErrorCode(1_030_001_003, "音频文件不存在");
|
||||
ErrorCode VOICE_TRANSCRIBE_FAILED = new ErrorCode(1_030_001_004, "语音识别失败");
|
||||
ErrorCode VOICE_TTS_FAILED = new ErrorCode(1_030_001_005, "语音合成失败");
|
||||
ErrorCode LATENTSYNC_SUBMIT_FAILED = new ErrorCode(1_030_001_101, "口型同步任务提交失败");
|
||||
|
||||
}
|
||||
|
||||
@@ -5,13 +5,22 @@ import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
|
||||
import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileConfigService;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.tik.file.dal.dataobject.TikUserFileDO;
|
||||
import cn.iocoder.yudao.module.tik.file.dal.mysql.TikUserFileMapper;
|
||||
@@ -60,6 +69,9 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
@Resource
|
||||
private FileMapper fileMapper;
|
||||
|
||||
@Resource
|
||||
private FileConfigService fileConfigService;
|
||||
|
||||
@Override
|
||||
public Long uploadFile(MultipartFile file, String fileCategory, String coverBase64) {
|
||||
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||
@@ -86,28 +98,52 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
throw exception(FILE_NOT_EXISTS, "文件读取失败");
|
||||
}
|
||||
|
||||
// ========== 第二阶段:上传到OSS(不在事务中,优先执行) ==========
|
||||
// 5. 上传文件到OSS(FileService会自动处理文件名,添加日期前缀和时间戳后缀)
|
||||
// FileService.createFile 会自动生成路径:{baseDirectory}/{yyyyMMdd}/{filename}_{timestamp}.ext
|
||||
// 注意:FileService 内部会使用原始文件名,并自动添加时间戳后缀保证唯一性
|
||||
// ========== 第二阶段:上传到OSS并保存文件记录(不在事务中,优先执行) ==========
|
||||
// 采用业界成熟方案:直接使用 fileMapper.insert() 获取文件ID,避免通过 URL 查询
|
||||
String fileUrl;
|
||||
String filePath;
|
||||
Long infraFileId = null; // 用于失败时删除OSS文件
|
||||
Long infraFileId;
|
||||
|
||||
try {
|
||||
fileUrl = fileApi.createFile(fileContent, file.getOriginalFilename(),
|
||||
baseDirectory, file.getContentType());
|
||||
|
||||
// 6. 从 infra_file 表查询实际的文件路径(确保路径100%准确)
|
||||
// 因为 FileService 已经保存了文件记录到 infra_file 表,我们可以通过 URL 查询获取准确的 path
|
||||
FileDO infraFile = getInfraFileByUrl(fileUrl, file.getSize());
|
||||
if (infraFile != null) {
|
||||
filePath = infraFile.getPath();
|
||||
infraFileId = infraFile.getId(); // 保存 infra_file.id,用于失败时删除
|
||||
} else {
|
||||
// 如果查询失败,从URL中提取路径(兜底方案)
|
||||
filePath = extractPathFromUrl(fileUrl);
|
||||
log.warn("[uploadFile][无法从infra_file表查询路径,使用URL提取,URL({})]", fileUrl);
|
||||
// 1. 处理文件名和类型
|
||||
String fileName = file.getOriginalFilename();
|
||||
String fileType = file.getContentType();
|
||||
if (StrUtil.isEmpty(fileType)) {
|
||||
fileType = FileTypeUtils.getMineType(fileContent, fileName);
|
||||
}
|
||||
if (StrUtil.isEmpty(fileName)) {
|
||||
fileName = DigestUtil.sha256Hex(fileContent);
|
||||
}
|
||||
if (StrUtil.isEmpty(FileUtil.extName(fileName))) {
|
||||
String extension = FileTypeUtils.getExtension(fileType);
|
||||
if (StrUtil.isNotEmpty(extension)) {
|
||||
fileName = fileName + "." + extension;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 生成上传路径(与 FileService 保持一致)
|
||||
filePath = generateUploadPath(fileName, baseDirectory);
|
||||
|
||||
// 3. 上传到OSS
|
||||
FileClient client = fileConfigService.getMasterFileClient();
|
||||
Assert.notNull(client, "客户端(master) 不能为空");
|
||||
String presignedUrl = client.upload(fileContent, filePath, fileType);
|
||||
|
||||
// 3.1 移除预签名URL中的签名参数,获取基础URL(用于存储)
|
||||
fileUrl = HttpUtils.removeUrlQuery(presignedUrl);
|
||||
|
||||
// 4. 保存到 infra_file 表,直接获取文件ID(MyBatis Plus 会自动填充自增ID)
|
||||
FileDO infraFile = new FileDO()
|
||||
.setConfigId(client.getId())
|
||||
.setName(fileName)
|
||||
.setPath(filePath)
|
||||
.setUrl(fileUrl)
|
||||
.setType(fileType)
|
||||
.setSize((int) file.getSize());
|
||||
fileMapper.insert(infraFile);
|
||||
infraFileId = infraFile.getId(); // MyBatis Plus 会自动填充自增ID
|
||||
|
||||
log.info("[uploadFile][文件上传成功,文件编号({}),路径({})]", infraFileId, filePath);
|
||||
} catch (Exception e) {
|
||||
log.error("[uploadFile][上传OSS失败]", e);
|
||||
throw exception(FILE_NOT_EXISTS, "上传OSS失败:" + e.getMessage());
|
||||
@@ -115,7 +151,7 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
|
||||
// ========== 第三阶段:保存数据库(在事务中,如果失败则删除OSS文件) ==========
|
||||
try {
|
||||
return saveFileRecord(userId, file, fileCategory, fileUrl, filePath, coverBase64, baseDirectory);
|
||||
return saveFileRecord(userId, file, fileCategory, fileUrl, filePath, coverBase64, baseDirectory, infraFileId);
|
||||
} catch (Exception e) {
|
||||
// 数据库保存失败,删除已上传的OSS文件
|
||||
log.error("[uploadFile][保存数据库失败,准备删除OSS文件,URL({})]", fileUrl, e);
|
||||
@@ -129,8 +165,14 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long saveFileRecord(Long userId, MultipartFile file, String fileCategory,
|
||||
String fileUrl, String filePath, String coverBase64, String baseDirectory) {
|
||||
// 7. 处理视频封面(如果有前端传递的 base64 封面,先处理封面再插入主记录)
|
||||
String fileUrl, String filePath, String coverBase64, String baseDirectory, Long infraFileId) {
|
||||
// 7. 验证 infraFileId 不为空(必须在保存记录之前检查)
|
||||
if (infraFileId == null) {
|
||||
log.error("[saveFileRecord][infra_file.id 为空,无法保存文件记录,用户({}),URL({})]", userId, fileUrl);
|
||||
throw exception(FILE_NOT_EXISTS, "文件记录保存失败:无法获取文件ID");
|
||||
}
|
||||
|
||||
// 8. 处理视频封面(如果有前端传递的 base64 封面,先处理封面再插入主记录)
|
||||
String coverUrl = null;
|
||||
if (StrUtil.isNotBlank(coverBase64) && StrUtil.containsIgnoreCase(file.getContentType(), "video")) {
|
||||
try {
|
||||
@@ -162,7 +204,8 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
|
||||
// 严格验证:确保返回的是有效的 URL,而不是 base64 字符串
|
||||
if (StrUtil.isNotBlank(uploadedUrl) && !uploadedUrl.equals(coverBase64) && !uploadedUrl.contains("data:image")) {
|
||||
coverUrl = uploadedUrl;
|
||||
// 移除预签名URL中的签名参数,获取基础URL(用于存储)
|
||||
coverUrl = HttpUtils.removeUrlQuery(uploadedUrl);
|
||||
log.info("[saveFileRecord][视频封面上传成功,封面URL({})]", coverUrl);
|
||||
} else {
|
||||
log.error("[saveFileRecord][视频封面上传返回无效URL,跳过保存封面。返回URL: {}", uploadedUrl);
|
||||
@@ -177,10 +220,10 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 创建文件记录(保存完整路径,包含封面URL和Base64)
|
||||
// 9. 创建文件记录(保存完整路径,包含封面URL和Base64)
|
||||
TikUserFileDO userFile = new TikUserFileDO()
|
||||
.setUserId(userId)
|
||||
.setFileId(null) // 显式设置为null,file_id是可选的,用于关联infra_file表
|
||||
.setFileId(infraFileId) // 关联infra_file表,用于后续通过FileService管理文件
|
||||
.setFileName(file.getOriginalFilename()) // 保存原始文件名,用于展示
|
||||
.setFileType(file.getContentType())
|
||||
.setFileCategory(fileCategory)
|
||||
@@ -191,11 +234,12 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
.setCoverBase64(StrUtil.isNotBlank(coverBase64) ? coverBase64 : null); // 保存原始base64数据(如果有)
|
||||
userFileMapper.insert(userFile);
|
||||
|
||||
// 9. 更新配额
|
||||
// 10. 更新配额
|
||||
quotaService.increaseUsedStorage(userId, file.getSize());
|
||||
|
||||
log.info("[saveFileRecord][用户({})保存文件记录成功,文件编号({})]", userId, userFile.getId());
|
||||
return userFile.getId();
|
||||
log.info("[saveFileRecord][用户({})保存文件记录成功,文件编号({}),infra文件编号({})]", userId, userFile.getId(), infraFileId);
|
||||
// 返回 infra_file.id,因为创建配音等操作需要使用 infra_file.id
|
||||
return infraFileId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,31 +265,41 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 infra_file 表查询文件信息(返回完整对象,包含 id)
|
||||
* 生成上传路径(与 FileService 保持一致)
|
||||
* 格式:{directory}/{yyyyMMdd}/{filename}_{timestamp}.ext
|
||||
*/
|
||||
private FileDO getInfraFileByUrl(String fileUrl, long fileSize) {
|
||||
if (StrUtil.isBlank(fileUrl)) {
|
||||
return null;
|
||||
private String generateUploadPath(String name, String directory) {
|
||||
// 1. 生成前缀、后缀
|
||||
String prefix = null;
|
||||
boolean PATH_PREFIX_DATE_ENABLE = true;
|
||||
boolean PATH_SUFFIX_TIMESTAMP_ENABLE = true;
|
||||
|
||||
if (PATH_PREFIX_DATE_ENABLE) {
|
||||
prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN);
|
||||
}
|
||||
try {
|
||||
// 移除URL中的查询参数(如果有)
|
||||
String cleanUrl = fileUrl;
|
||||
if (fileUrl.contains("?")) {
|
||||
cleanUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
|
||||
String suffix = null;
|
||||
if (PATH_SUFFIX_TIMESTAMP_ENABLE) {
|
||||
suffix = String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// 2.1 先拼接 suffix 后缀
|
||||
if (StrUtil.isNotEmpty(suffix)) {
|
||||
String ext = FileUtil.extName(name);
|
||||
if (StrUtil.isNotEmpty(ext)) {
|
||||
name = FileUtil.mainName(name) + "_" + suffix + "." + ext;
|
||||
} else {
|
||||
name = name + "_" + suffix;
|
||||
}
|
||||
|
||||
// 通过 URL 和文件大小查询(提高准确性)
|
||||
return fileMapper.selectOne(
|
||||
new LambdaQueryWrapperX<FileDO>()
|
||||
.eq(FileDO::getUrl, cleanUrl)
|
||||
.eq(FileDO::getSize, (int) fileSize) // FileDO.size 是 Integer
|
||||
.orderByDesc(FileDO::getCreateTime)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("[getInfraFileByUrl][查询infra_file表失败,URL({})]", fileUrl, e);
|
||||
}
|
||||
return null;
|
||||
// 2.2 再拼接 prefix 前缀
|
||||
if (StrUtil.isNotEmpty(prefix)) {
|
||||
name = prefix + "/" + name;
|
||||
}
|
||||
// 2.3 最后拼接 directory 目录
|
||||
if (StrUtil.isNotEmpty(directory)) {
|
||||
name = directory + "/" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -466,16 +520,28 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 移除URL中的查询参数(签名参数等)
|
||||
String cleanUrl = url;
|
||||
if (url.contains("?")) {
|
||||
cleanUrl = url.substring(0, url.indexOf("?"));
|
||||
}
|
||||
|
||||
// 如果URL包含域名,提取路径部分
|
||||
if (url.contains("://")) {
|
||||
int pathStart = url.indexOf("/", url.indexOf("://") + 3);
|
||||
if (cleanUrl.contains("://")) {
|
||||
int pathStart = cleanUrl.indexOf("/", cleanUrl.indexOf("://") + 3);
|
||||
if (pathStart > 0) {
|
||||
return url.substring(pathStart);
|
||||
String fullPath = cleanUrl.substring(pathStart);
|
||||
// 路径可能包含 bucket 名称,需要提取实际的文件路径
|
||||
// 例如:/bucket-name/user-id/tenant-id/voice/20251117/file.wav
|
||||
// 实际 path 可能是:user-id/tenant-id/voice/20251117/file.wav
|
||||
// 但数据库中的 path 格式是:voice/20251117/file_timestamp.wav
|
||||
// 所以我们需要找到包含日期格式的部分(yyyyMMdd)
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
// 如果已经是路径格式,直接返回
|
||||
if (url.startsWith("/")) {
|
||||
return url;
|
||||
// 如果已经是路径格式,直接返回(去除查询参数)
|
||||
if (cleanUrl.startsWith("/")) {
|
||||
return cleanUrl;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[extractPathFromUrl][从URL提取路径失败,URL({})]", url, e);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class TikFileTransCharacters {
|
||||
// 设置是否输出词信息,默认为false,开启时需要设置version为4.0及以上。
|
||||
taskObject.put(KEY_ENABLE_WORDS, true);
|
||||
String task = taskObject.toJSONString();
|
||||
System.out.println(task);
|
||||
System.out.println("[TikFileTransCharacters][submitFileTransRequest] 请求参数: " + task);
|
||||
// 设置以上JSON字符串为Body参数。
|
||||
postRequest.putBodyParameter(KEY_TASK, task);
|
||||
// 设置为POST方式的请求。
|
||||
@@ -85,15 +85,24 @@ public class TikFileTransCharacters {
|
||||
String taskId = null;
|
||||
try {
|
||||
CommonResponse postResponse = client.getCommonResponse(postRequest);
|
||||
System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());
|
||||
if (postResponse.getHttpStatus() == 200) {
|
||||
System.err.println("[TikFileTransCharacters][submitFileTransRequest] 提交录音文件识别请求的响应:" + postResponse.getData());
|
||||
int httpStatus = postResponse.getHttpStatus();
|
||||
System.out.println("[TikFileTransCharacters][submitFileTransRequest] HTTP状态码: " + httpStatus);
|
||||
if (httpStatus == 200) {
|
||||
JSONObject result = JSONObject.parseObject(postResponse.getData());
|
||||
String statusText = result.getString(KEY_STATUS_TEXT);
|
||||
System.out.println("[TikFileTransCharacters][submitFileTransRequest] 状态文本: " + statusText);
|
||||
if (STATUS_SUCCESS.equals(statusText)) {
|
||||
taskId = result.getString(KEY_TASK_ID);
|
||||
System.out.println("[TikFileTransCharacters][submitFileTransRequest] 任务ID: " + taskId);
|
||||
} else {
|
||||
System.err.println("[TikFileTransCharacters][submitFileTransRequest] 状态不是SUCCESS,状态文本: " + statusText);
|
||||
}
|
||||
} else {
|
||||
System.err.println("[TikFileTransCharacters][submitFileTransRequest] HTTP状态码不是200,状态码: " + httpStatus + ",响应: " + postResponse.getData());
|
||||
}
|
||||
} catch (ClientException e) {
|
||||
System.err.println("[TikFileTransCharacters][submitFileTransRequest] 异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return taskId;
|
||||
@@ -120,17 +129,25 @@ public class TikFileTransCharacters {
|
||||
* 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”或错误描述,则结束轮询。
|
||||
*/
|
||||
String result = null;
|
||||
int pollCount = 0;
|
||||
while (true) {
|
||||
pollCount++;
|
||||
try {
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 第" + pollCount + "次轮询,taskId: " + taskId);
|
||||
CommonResponse getResponse = client.getCommonResponse(getRequest);
|
||||
System.err.println("识别查询结果:" + getResponse.getData());
|
||||
if (getResponse.getHttpStatus() != 200) {
|
||||
int httpStatus = getResponse.getHttpStatus();
|
||||
String responseData = getResponse.getData();
|
||||
System.err.println("[TikFileTransCharacters][getFileTransResult] 识别查询结果,HTTP状态码: " + httpStatus + ",响应: " + responseData);
|
||||
if (httpStatus != 200) {
|
||||
System.err.println("[TikFileTransCharacters][getFileTransResult] HTTP状态码不是200,停止轮询,taskId: " + taskId);
|
||||
break;
|
||||
}
|
||||
JSONObject rootObj = JSONObject.parseObject(getResponse.getData());
|
||||
JSONObject rootObj = JSONObject.parseObject(responseData);
|
||||
String statusText = rootObj.getString(KEY_STATUS_TEXT);
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 状态文本: " + statusText);
|
||||
if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {
|
||||
// 继续轮询,注意设置轮询时间间隔。
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 任务进行中,等待10秒后继续轮询,taskId: " + taskId);
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
else {
|
||||
@@ -139,15 +156,22 @@ public class TikFileTransCharacters {
|
||||
result = rootObj.getString(KEY_RESULT);
|
||||
// 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空。
|
||||
if(result == null) {
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 识别成功但结果为空,taskId: " + taskId);
|
||||
result = "";
|
||||
} else {
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 识别成功,结果长度: " + result.length() + ",taskId: " + taskId);
|
||||
}
|
||||
} else {
|
||||
System.err.println("[TikFileTransCharacters][getFileTransResult] 状态不是SUCCESS,状态文本: " + statusText + ",taskId: " + taskId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("[TikFileTransCharacters][getFileTransResult] 轮询异常,taskId: " + taskId + ",异常信息: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
System.out.println("[TikFileTransCharacters][getFileTransResult] 轮询结束,taskId: " + taskId + ",结果: " + (result != null ? "非空,长度" + result.length() : "null"));
|
||||
return result;
|
||||
}
|
||||
public static void main(String args[]) throws Exception {
|
||||
|
||||
@@ -165,17 +165,20 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
|
||||
@Override
|
||||
public Object videoToCharacters(String fileLink){
|
||||
log.info("[videoToCharacters][开始识别,文件链接({})]", fileLink);
|
||||
TikFileTransCharacters tikFileTransCharacters = new TikFileTransCharacters(accessKeyId, accessKeySecret);
|
||||
// 第一步:提交录音文件识别请求,获取任务ID用于后续的识别结果轮询。
|
||||
String taskId = tikFileTransCharacters.submitFileTransRequest(appKey, fileLink);
|
||||
if (taskId == null) {
|
||||
log.error("[videoToCharacters][提交识别请求失败,taskId为null,fileLink({})]", fileLink);
|
||||
return CommonResult.error(500,"录音文件识别请求失败!");
|
||||
}
|
||||
// 第二步:根据任务ID轮询识别结果。
|
||||
log.info("[videoToCharacters][提交识别请求成功,taskId({})]", taskId);
|
||||
String transResult = tikFileTransCharacters.getFileTransResult(taskId);
|
||||
if (transResult == null) {
|
||||
log.error("[videoToCharacters][识别结果查询失败,taskId({}),transResult为null]", taskId);
|
||||
return CommonResult.error(501,"录音文件识别请求失败!");
|
||||
}
|
||||
log.info("[videoToCharacters][识别成功,taskId({}),结果长度({})]", taskId, transResult.length());
|
||||
return CommonResult.success(transResult);
|
||||
}
|
||||
|
||||
@@ -183,30 +186,28 @@ public class TikHupServiceImpl implements TikHupService {
|
||||
|
||||
@Override
|
||||
public Object videoToCharacters2(List<String> fileLinkList){
|
||||
// 创建转写请求参数
|
||||
TranscriptionParam param =
|
||||
TranscriptionParam.builder()
|
||||
// 若没有将API Key配置到环境变量中,需将apiKey替换为自己的API Key
|
||||
.apiKey(apiKey)
|
||||
.model("paraformer-v1")
|
||||
// “language_hints”只支持paraformer-v2模型
|
||||
.parameter("language_hints", new String[]{"zh", "en"})
|
||||
.fileUrls(fileLinkList)
|
||||
.build();
|
||||
log.info("[videoToCharacters2][开始识别,文件数量({}),文件URL({})]",
|
||||
fileLinkList != null ? fileLinkList.size() : 0, fileLinkList);
|
||||
TranscriptionParam param = TranscriptionParam.builder()
|
||||
.apiKey(apiKey)
|
||||
.model("paraformer-v1")
|
||||
.parameter("language_hints", new String[]{"zh", "en"})
|
||||
.fileUrls(fileLinkList)
|
||||
.build();
|
||||
try {
|
||||
Transcription transcription = new Transcription();
|
||||
// 提交转写请求
|
||||
TranscriptionResult result = transcription.asyncCall(param);
|
||||
log.info("RequestId: {}" ,result.getRequestId());
|
||||
// 阻塞等待任务完成并获取结果
|
||||
log.info("[videoToCharacters2][提交转写请求成功,TaskId({})]", result.getTaskId());
|
||||
result = transcription.wait(
|
||||
TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
|
||||
return CommonResult.success(new GsonBuilder().setPrettyPrinting().create().toJson(result.getOutput()));
|
||||
String outputJson = new GsonBuilder().setPrettyPrinting().create().toJson(result.getOutput());
|
||||
log.info("[videoToCharacters2][识别成功,TaskId({}),结果长度({})]",
|
||||
result.getTaskId(), outputJson != null ? outputJson.length() : 0);
|
||||
return CommonResult.success(outputJson);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage());
|
||||
log.error("[videoToCharacters2][识别失败,文件URL({}),异常({})]", fileLinkList, e.getMessage(), e);
|
||||
return CommonResult.error(500,"录音文件识别请求失败!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user