This commit is contained in:
2026-02-23 23:36:25 +08:00
parent 02a7cf1981
commit f63e6b92bd
3 changed files with 245 additions and 272 deletions

View File

@@ -3,21 +3,12 @@ package cn.iocoder.yudao.module.tik.tikhup.controller;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.tik.tikhup.mapper.TikPromptMapper;
import cn.iocoder.yudao.module.tik.tikhup.service.TikHupService;
import com.alibaba.dashscope.app.Application;
import com.alibaba.dashscope.app.ApplicationParam;
import com.alibaba.dashscope.app.ApplicationResult;
import com.alibaba.dashscope.app.FlowStreamMode;
import com.alibaba.dashscope.utils.Constants;
import com.alibaba.dashscope.utils.JsonUtils;
import com.alibaba.fastjson.JSON;
import io.reactivex.Flowable;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import java.util.List;
import java.util.Map;
@@ -30,118 +21,50 @@ import java.util.Map;
public class TikHupController {
private final TikHupService tikHupService;
private final TikPromptMapper tikPromptMapper;
@GetMapping("/fetch_user_post_videos")
@Operation(
summary = "获取用户主页作品数据",
description = "通过TikHup API获取指定用户的TikTok作品数据包括视频列表、用户信息等"
)
public Object fetch_user_post_videos(
@RequestParam String type,
@RequestParam String sec_user_id,
@RequestParam int max_cursor,
@RequestParam int count) {
return tikHupService.fetch_user_post_videos(type, sec_user_id,max_cursor, count);
}
@PostMapping("/postTikHup")
@Operation(
summary = "获取用户主页作品数据",
description = "通过TikHup API获取指定用户的TikTok作品数据包括视频列表、用户信息等"
)
public Object postTikHup(@RequestParam String type,@RequestParam String methodType,@RequestParam String urlParams,@RequestParam String paramType) {
return tikHupService.postTikHup(type, methodType,urlParams,paramType);
@Operation(summary = "获取用户主页作品数据", description = "通过TikHup API获取指定用户的TikTok作品数据")
public Object postTikHup(@RequestParam String type,
@RequestParam String methodType,
@RequestParam String urlParams,
@RequestParam String paramType) {
return tikHupService.postTikHup(type, methodType, urlParams, paramType);
}
@PostMapping("/videoToCharacters")
@Operation(summary = "音频转文字", description = "音频转文字接口")
public Object videoToCharacters(@RequestBody Map<String,Object> fileLinkMap) {
public Object videoToCharacters(@RequestBody Map<String, Object> fileLinkMap) {
String fileLink = (String) fileLinkMap.get("fileLink");
return tikHupService.videoToCharacters(fileLink);
}
@PostMapping("/videoToCharacters2")
@Operation(summary = "音频转文字", description = "音频转文字接口")
public Object videoToCharacters2(@RequestBody Map<String,Object> fileLinkMap) {
@Operation(summary = "批量音频转文字", description = "批量音频转文字接口")
public Object videoToCharacters2(@RequestBody Map<String, Object> fileLinkMap) {
@SuppressWarnings("unchecked")
List<String> fileLinkList = (List<String>) fileLinkMap.get("fileLinkList");
return tikHupService.videoToCharacters2(fileLinkList);
}
@GetMapping("/getPromptTypeList")
@Operation(summary = "获取提示词类型列表", description = "获取提示词类型列表")
public Object getPromptTypeList(){
public Object getPromptTypeList() {
return CommonResult.success(tikPromptMapper.getTikPromptVOList());
}
@PostMapping("/deepseekAnalysis")
@Operation(summary = "deepseek文案重写", description = "deepseek文案重写")
public Object deepseekAnalysis(@RequestBody Map<String,Object> contentMap){
public Object deepseekAnalysis(@RequestBody Map<String, Object> contentMap) {
String content = (String) contentMap.get("content");
String promptType = (String) contentMap.get("promptType");
return tikHupService.deepseekAnalysis(promptType,content);
return tikHupService.deepseekAnalysis(promptType, content);
}
@PostMapping("/callWorkflow")
@Operation(summary = "调用阿里工作流", description = "调用阿里工作流 文档地址https://help.aliyun.com/zh/model-studio/invoke-workflow-application?mode=pure#8f4b4d2ce9fry")
public Flux<String> callWorkflow(@RequestBody Map<String,Object> params){
Constants.baseHttpApiUrl = "https://dashscope.aliyuncs.com/api/v1";
ApplicationParam param = ApplicationParam.builder()
// 若没有配置环境变量可用百炼API Key将下行替换为.apiKey("sk-xxx")。但不建议在生产环境中直接将API Key硬编码到代码中以减少API Key泄露风险。
.apiKey("sk-10c746f8cb8640738f8d6b71af699003")
.appId("bb746b74bfb0439e841cb309dc9f945c")
.flowStreamMode(FlowStreamMode.MESSAGE_FORMAT)
.bizParams(JsonUtils.parse(JSON.toJSONString(params)))
.prompt((String) params.get("audio_prompt"))
.hasThoughts(false)
.incrementalOutput(true)
.build();
Application application = new Application();
try{
//ApplicationResult result = application.call(param);
//return CommonResult.success(result.getOutput().getText());
Flowable<ApplicationResult> flowableResult = application.streamCall(param);
// 关键RxJava Flowable 转 WebFlux Flux指定非阻塞线程调度
// 4.2 将 RxJava Flowable 转换为 WebFlux Flux响应式转换无阻塞
return Flux.from(flowableResult)
// 关键:切换到非阻塞线程执行(避免阻塞 WebFlux 事件循环)
.subscribeOn(Schedulers.boundedElastic())
// 4.3 处理每一条流式数据:提取 thoughts 字段(按你的需求,也可提取 text
.map(applicationResult -> {
// 校验结果非 null阿里 SDK 可能返回空结果)
if (applicationResult == null || applicationResult.getOutput() == null) {
log.warn("收到空的流式结果");
return "空结果";
}
return applicationResult.getOutput().getWorkflowMessage().getMessage().getContent();
})
// 【新增】设置超时时间30分钟内没有数据则超时
.timeout(java.time.Duration.ofMinutes(30))
// 【新增】超时后返回错误信息
.onErrorResume(java.util.concurrent.TimeoutException.class, e -> {
log.warn("流式输出超时30分钟无数据连接已断开");
return Flux.just("[TIMEOUT]流式输出超时,请检查网络或重新请求");
})
// 【新增】流式输出完成后,发送结束标记
.concatWith(Flux.just("[DONE]"))
// 4.4 流处理异常捕获(如网络中断、阿里 SDK 内部错误)
.onErrorMap(e -> {
log.error("流式处理异常", e); // 打印完整异常栈,便于排查
return new RuntimeException("流式处理错误:" + e.getMessage());
})
// 4.5 异常时返回默认提示(前端不会断连,而是接收错误信息)
.onErrorReturn("错误:流式处理中断:" + "请重试或联系管理员");
}catch (Exception e){
// 捕获 SDK 调用时的同步异常(如初始化失败、网络连接超时)
log.error("调用阿里 SDK 失败", e);
return Flux.just("错误SDK 调用失败:" + e.getMessage());
}
@Operation(summary = "调用阿里工作流", description = "调用阿里工作流进行流式处理")
public Flux<String> callWorkflow(@RequestBody Map<String, Object> params) {
return tikHupService.callWorkflow(params);
}
}

View File

@@ -1,20 +1,13 @@
package cn.iocoder.yudao.module.tik.tikhup.service;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
public interface TikHupService {
/**
* 获取用户主页作品数据
* @param sec_user_id 类型
* @param sec_user_id 用户
* @param count 查询总数
* @return Response
*/
Object fetch_user_post_videos(String type,String sec_user_id,int max_cursor, int count);
Object postTikHup(String type,String methodType,String urlParams,String paramType);
Object postTikHup(String type, String methodType, String urlParams, String paramType);
/**
* 音频转文字
@@ -31,11 +24,18 @@ public interface TikHupService {
Object videoToCharacters2(List<String> fileLinkList);
/**
*
* deepseek 文案改写
* @param promptType 提示词类型
* @param content 提示词内容
* @return deepseek改写后的内容
*/
Object deepseekAnalysis(String promptType,String content);
Object deepseekAnalysis(String promptType, String content);
/**
* 调用阿里云工作流
* @param params 业务参数
* @return 流式响应
*/
Flux<String> callWorkflow(Map<String, Object> params);
}

View File

@@ -8,85 +8,62 @@ import cn.iocoder.yudao.module.tik.tikhup.mapper.TikPromptMapper;
import cn.iocoder.yudao.module.tik.tikhup.mapper.TikTokenMapper;
import cn.iocoder.yudao.module.tik.tikhup.vo.TikPromptVO;
import cn.iocoder.yudao.module.tik.tikhup.vo.TikTokenVO;
import com.alibaba.dashscope.app.Application;
import com.alibaba.dashscope.app.ApplicationParam;
import com.alibaba.dashscope.app.ApplicationResult;
import com.alibaba.dashscope.app.FlowStreamMode;
import com.alibaba.dashscope.audio.asr.transcription.Transcription;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionQueryParam;
import com.alibaba.dashscope.audio.asr.transcription.TranscriptionResult;
import com.alibaba.dashscope.utils.Constants;
import com.alibaba.dashscope.utils.JsonUtils;
import com.alibaba.fastjson.JSON;
import com.google.gson.GsonBuilder;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import io.reactivex.Flowable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
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.TimeoutException;
@Slf4j
@Service
@RequiredArgsConstructor
public class TikHupServiceImpl implements TikHupService {
/** TikHub 平台标识 */
// 平台和模型标识
private static final String PLATFORM_TIKHUB = "tikhub";
/** TikHub 数据抓取模型类型 */
private static final String MODEL_TYPE_FETCH = "fetch";
private static final String MODEL_TYPE_CRAWLER = "crawler";
// API 配置(建议后续迁移到配置文件)
private static final String ALIYUN_API_URL = "https://dashscope.aliyuncs.com/api/v1";
private static final String ALIYUN_API_KEY = "sk-10c746f8cb8640738f8d6b71af699003";
private static final String ALIYUN_APP_ID = "bb746b74bfb0439e841cb309dc9f945c";
private static final String DEEPSEEK_API_KEY = "sk-7f666f993b144d279ae375a015e4de56";
private static final String DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions";
// 阿里云语音识别配置
private final String appKey = "sldJ4XSpYp3rKALZ";
private final String accessKeyId = "LTAI5tPV9Ag3csf41GZjaLTA";
private final String accessKeySecret = "kDqlGeJTKw6tJtFYiaY8vQTFuVIQDs";
// 流式处理配置
private static final Duration STREAM_TIMEOUT = Duration.ofMinutes(30);
private final TikTokenMapper tikTokenMapper;
private final TikPromptMapper tikPromptMapper;
private final PointsService pointsService;
@Override
public Object fetch_user_post_videos(String type,String sec_user_id, int max_cursor, int count){
// 1. 获取当前用户ID
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String userId = loginUserId != null ? loginUserId.toString() : "1";
// 2. 获取积分配置并预检
AiModelConfigDO config = null;
try {
config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_TYPE_FETCH);
pointsService.checkPoints(userId, config.getConsumePoints());
} catch (Exception e) {
log.error("[fetch_user_post_videos] 积分预检失败: {}", e.getMessage());
return CommonResult.error(400, e.getMessage());
}
String url = StringUtils.EMPTY;
if("xiaohongshu".equals(type)){
url = "https://api.tikhub.io/api/v1/xiaohongshu/app/get_user_info";
} else if("tik-web".equals(type)){
url = "https://api.tikhub.io/api/v1/douyin/web/fetch_user_post_videos";
} else if("tik-app".equals(type)){
url = "https://api.tikhub.io/api/v1/douyin/app/v3/fetch_user_post_videos";
}
TikTokenVO tikTokenVO = tikTokenMapper.getPlatformToken(url);
String Authorization = tikTokenVO.getPlatformToken();
try{
Unirest.setTimeouts(0, 0);
HttpResponse<String> response = Unirest.get(url+"?sec_user_id="+sec_user_id+"&max_cursor="+max_cursor+"&count="+count)
.header("Authorization", "Bearer "+Authorization)
.asString();
if(response.getStatus() == 200){
// 3. API 调用成功,扣减积分
try {
pointsService.deductPoints(userId, config.getConsumePoints(), "tikhub_fetch", type);
log.info("[fetch_user_post_videos] 用户 {} 扣减 {} 积分", userId, config.getConsumePoints());
} catch (Exception e) {
log.error("[fetch_user_post_videos] 积分扣减失败: {}", e.getMessage());
}
return JSON.parseObject(response.getBody());
}
}catch (Exception e){
log.error("fetch_user_post_videos接口调用异常");
}
return new HashMap<>();
}
@Override
public Object postTikHup(String type, String methodType, String urlParams, String paramType) {
// 1. 参数校验
@@ -102,182 +79,255 @@ public class TikHupServiceImpl implements TikHupService {
// 2. 获取当前用户ID并预检积分
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String userId = loginUserId != null ? loginUserId.toString() : "1";
AiModelConfigDO config = null;
AiModelConfigDO config;
try {
config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_TYPE_FETCH);
config = pointsService.getConfig(PLATFORM_TIKHUB, MODEL_TYPE_CRAWLER);
pointsService.checkPoints(userId, config.getConsumePoints());
} catch (Exception e) {
log.error("[postTikHup] 积分预检失败: {}", e.getMessage());
return CommonResult.error(400, e.getMessage());
}
// 3. 获取接口配置信息
// 3. 从 muye_ai_model_config 获取 apiKey
String authorization = config.getApiKey();
if (StringUtils.isBlank(authorization)) {
log.error("postTikHup: TikHub 配置的 apiKey 为空");
return CommonResult.error(500, "接口配置错误apiKey 为空");
}
// 4. 获取接口 URL
TikTokenVO tikTokenVO = tikTokenMapper.getInterfaceUrl(type);
if (tikTokenVO == null) {
log.error("postTikHup: 未找到接口类型 {} 的配置信息", type);
return CommonResult.error(404, "未找到接口类型 " + type + " 的配置信息");
log.error("postTikHup: 未找到接口类型 {} 的 URL 配置", type);
return CommonResult.error(404, "未找到接口类型 " + type + " URL 配置");
}
String authorization = tikTokenVO.getPlatformToken();
String url = tikTokenVO.getPlatformUrl();
if (StringUtils.isBlank(authorization)) {
log.error("postTikHup: 接口类型 {} 的 token 为空", type);
return CommonResult.error(500, "接口配置错误token 为空");
}
if (StringUtils.isBlank(url)) {
log.error("postTikHup: 接口类型 {} 的 URL 为空", type);
return CommonResult.error(500, "接口配置错误URL 为空");
}
// 4. 统一转换为小写进行比较(兼容大小写)
// 5. 统一转换为小写进行比较
String methodTypeLower = methodType.toLowerCase();
String paramTypeLower = paramType != null ? paramType.toLowerCase() : "";
try {
Unirest.setTimeouts(0, 0);
HttpResponse<String> response;
HttpResponse<String> response = executeRequest(url, urlParams, authorization, methodTypeLower, paramTypeLower);
// 5. 根据请求方法和参数类型构建请求
if ("post".equals(methodTypeLower) && "json".equals(paramTypeLower)) {
// POST + JSON: 将 urlParams 作为 JSON body
log.debug("postTikHup: POST JSON 请求, URL: {}, Body: {}", url, urlParams);
response = Unirest.post(url)
.header("Authorization", "Bearer " + authorization)
.header("Content-Type", "application/json")
.body(urlParams)
.asString();
} else if ("post".equals(methodTypeLower)) {
// POST + 表单参数: 将 urlParams 作为 URL 查询参数
log.debug("postTikHup: POST 表单请求, URL: {}?{}", url, urlParams);
response = Unirest.post(url + "?" + urlParams)
.header("Authorization", "Bearer " + authorization)
.asString();
} else {
// GET 或其他方法: 将 urlParams 作为 URL 查询参数
// 处理 URL 拼接:如果 URL 已包含查询参数,使用 & 连接,否则使用 ? 连接
String finalUrl = url;
if (urlParams != null && !urlParams.trim().isEmpty()) {
if (url.contains("?")) {
// URL 已包含查询参数,使用 & 连接
finalUrl = url + "&" + urlParams;
} else {
// URL 不包含查询参数,使用 ? 连接
finalUrl = url + "?" + urlParams;
}
}
log.info("postTikHup: GET 请求, 原始URL: {}, 参数: {}, 最终URL: {}", url, urlParams, finalUrl);
response = Unirest.get(finalUrl)
.header("Authorization", "Bearer " + authorization)
.asString();
}
// 6. 检查响应状态码
int statusCode = response.getStatus();
String responseBody = response.getBody();
if (statusCode == 200) {
if (StringUtils.isNotBlank(responseBody)) {
// 7. API 调用成功,扣减积分
try {
pointsService.deductPoints(userId, config.getConsumePoints(), "tikhub_fetch", type);
log.info("[postTikHup] 用户 {} 扣减 {} 积分", userId, config.getConsumePoints());
} catch (Exception e) {
log.error("[postTikHup] 积分扣减失败: {}", e.getMessage());
}
// 尝试解析为 JSON如果失败则直接返回字符串
try {
return JSON.parseObject(responseBody);
} catch (Exception e) {
// 如果不是 JSON直接返回字符串
return responseBody;
}
} else {
log.warn("postTikHup: 接口返回空响应, URL: {}", url);
return CommonResult.error(500, "接口返回空响应");
}
} else {
log.error("postTikHup: 接口调用失败, URL: {}, 状态码: {}, 响应: {}", url, statusCode, responseBody);
return CommonResult.error(statusCode, "接口调用失败: " + (StringUtils.isNotBlank(responseBody) ? responseBody : "HTTP " + statusCode));
}
// 6. 处理响应
return handleResponse(response, url, userId, config.getConsumePoints(), type);
} catch (Exception e) {
log.error("postTikHup: 接口调用异常, URL: {}, 错误信息: {}", url, e.getMessage(), e);
return CommonResult.error(500, "接口调用异常: " + e.getMessage());
}
}
private final String appKey = "sldJ4XSpYp3rKALZ ";
private final String accessKeyId = "LTAI5tPV9Ag3csf41GZjaLTA";
private final String accessKeySecret = "kDqlGeJTKw6tJtFYiaY8vQTFuVIQDs";
private final String deepseekApiKey="sk-7f666f993b144d279ae375a015e4de56";
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);
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);
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();
}
}
private String buildUrlWithParams(String url, String urlParams) {
if (StringUtils.isBlank(urlParams)) {
return url;
}
return url.contains("?") ? url + "&" + urlParams : url + "?" + urlParams;
}
private Object handleResponse(HttpResponse<String> response, String url, String userId,
Integer consumePoints, String type) {
int statusCode = 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 (StringUtils.isBlank(responseBody)) {
log.warn("postTikHup: 接口返回空响应, URL: {}", url);
return CommonResult.error(500, "接口返回空响应");
}
// 扣减积分
deductPointsSafely(userId, consumePoints, type);
// 尝试解析 JSON失败则返回原始字符串
try {
return JSON.parseObject(responseBody);
} catch (Exception e) {
return responseBody;
}
}
private void deductPointsSafely(String userId, Integer consumePoints, String type) {
try {
pointsService.deductPoints(userId, consumePoints, "tikhub_fetch", type);
log.info("[postTikHup] 用户 {} 扣减 {} 积分", userId, consumePoints);
} catch (Exception e) {
log.error("[postTikHup] 积分扣减失败: {}", e.getMessage());
}
}
@Override
public Object videoToCharacters(String fileLink){
log.info("[videoToCharacters][开始识别,文件链接({})]", fileLink);
TikFileTransCharacters tikFileTransCharacters = new TikFileTransCharacters(accessKeyId, accessKeySecret);
String taskId = tikFileTransCharacters.submitFileTransRequest(appKey, fileLink);
public Object videoToCharacters(String fileLink) {
log.info("[videoToCharacters] 开始识别,文件链接: {}", fileLink);
TikFileTransCharacters transCharacters = new TikFileTransCharacters(accessKeyId, accessKeySecret);
String taskId = transCharacters.submitFileTransRequest(appKey, fileLink);
if (taskId == null) {
log.error("[videoToCharacters][提交识别请求失败taskId为nullfileLink({})]", fileLink);
return CommonResult.error(500,"录音文件识别请求失败!");
log.error("[videoToCharacters] 提交识别请求失败taskId为nullfileLink: {}", fileLink);
return CommonResult.error(500, "录音文件识别请求失败!");
}
log.info("[videoToCharacters][提交识别请求成功taskId({})]", taskId);
String transResult = tikFileTransCharacters.getFileTransResult(taskId);
log.info("[videoToCharacters] 提交识别请求成功taskId: {}", taskId);
String transResult = transCharacters.getFileTransResult(taskId);
if (transResult == null) {
log.error("[videoToCharacters][识别结果查询失败taskId({})transResult为null]", taskId);
return CommonResult.error(501,"录音文件识别请求失败!");
log.error("[videoToCharacters] 识别结果查询失败taskId: {}", taskId);
return CommonResult.error(501, "录音文件识别请求失败!");
}
log.info("[videoToCharacters][识别成功taskId({}),结果长度({})]", taskId, transResult.length());
log.info("[videoToCharacters] 识别成功taskId: {},结果长度: {}", taskId, transResult.length());
return CommonResult.success(transResult);
}
private final String apiKey = "sk-10c746f8cb8640738f8d6b71af699003";
@Override
public Object videoToCharacters2(List<String> fileLinkList){
log.info("[videoToCharacters2][开始识别,文件数量({})文件URL({})]",
public Object videoToCharacters2(List<String> fileLinkList) {
log.info("[videoToCharacters2] 开始识别,文件数量: {}文件URL: {}",
fileLinkList != null ? fileLinkList.size() : 0, fileLinkList);
TranscriptionParam param = TranscriptionParam.builder()
.apiKey(apiKey)
.apiKey(ALIYUN_API_KEY)
.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("[videoToCharacters2][提交转写请求成功TaskId({})]", result.getTaskId());
result = transcription.wait(
TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
log.info("[videoToCharacters2] 提交转写请求成功TaskId: {}", result.getTaskId());
result = transcription.wait(TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
String outputJson = new GsonBuilder().setPrettyPrinting().create().toJson(result.getOutput());
log.info("[videoToCharacters2][识别成功TaskId({}),结果长度({})]",
log.info("[videoToCharacters2] 识别成功TaskId: {},结果长度: {}",
result.getTaskId(), outputJson != null ? outputJson.length() : 0);
return CommonResult.success(outputJson);
} catch (Exception e) {
log.error("[videoToCharacters2][识别失败文件URL({}),异常({})]", fileLinkList, e.getMessage(), e);
return CommonResult.error(500,"录音文件识别请求失败!");
log.error("[videoToCharacters2] 识别失败文件URL: {},异常: {}", fileLinkList, e.getMessage(), e);
return CommonResult.error(500, "录音文件识别请求失败!");
}
}
@Override
public Object deepseekAnalysis(String promptType, String content) {
TikPromptVO tikPromptVO = tikPromptMapper.getTikPromptVO(promptType);
if (tikPromptVO == null) {
log.error("[deepseekAnalysis] 未找到提示词类型: {}", promptType);
return CommonResult.error(404, "未找到提示词类型");
}
public Object deepseekAnalysis(String promptType,String content){
Unirest.setTimeouts(0, 0);
try{
TikPromptVO tikPromptVO = tikPromptMapper.getTikPromptVO(promptType);
HttpResponse<String> response = Unirest.post("https://api.deepseek.com/chat/completions")
try {
String requestBody = buildDeepseekRequestBody(tikPromptVO.getPrompt(), content);
HttpResponse<String> response = Unirest.post(DEEPSEEK_API_URL)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + deepseekApiKey)
.body("{\n \"messages\": [\n {\n \"content\": \""+tikPromptVO.getPrompt()+"\n\n\n原文如下\n"+content+"\",\n \"role\": \"system\"\n },\n {\n \"content\": \"Hi\",\n \"role\": \"user\"\n }\n ],\n \"model\": \"deepseek-chat\",\n \"frequency_penalty\": 0,\n \"max_tokens\": 8000,\n \"presence_penalty\": 0,\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stop\": null,\n \"stream\": false,\n \"stream_options\": null,\n \"temperature\": 1,\n \"top_p\": 1,\n \"tools\": null,\n \"tool_choice\": \"none\",\n \"logprobs\": false,\n \"top_logprobs\": null\n}")
.header("Authorization", "Bearer " + DEEPSEEK_API_KEY)
.body(requestBody)
.asString();
if(response.getStatus() == 200){
if (response.getStatus() == 200) {
return response.getBody();
}
}catch (Exception e){
log.error(e.getMessage());
return CommonResult.error(500,"文案改写失败!");
log.error("[deepseekAnalysis] 调用失败,状态码: {}", response.getStatus());
return CommonResult.error(500, "文案改写失败!");
} catch (Exception e) {
log.error("[deepseekAnalysis] 异常: {}", e.getMessage(), e);
return CommonResult.error(500, "文案改写失败!");
}
return null;
}
private String buildDeepseekRequestBody(String prompt, String content) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "deepseek-chat");
requestBody.put("frequency_penalty", 0);
requestBody.put("max_tokens", 8000);
requestBody.put("presence_penalty", 0);
requestBody.put("stream", false);
requestBody.put("temperature", 1);
requestBody.put("top_p", 1);
requestBody.put("logprobs", false);
List<Map<String, String>> messages = List.of(
Map.of("role", "system", "content", prompt + "\n\n\n原文如下\n" + content),
Map.of("role", "user", "content", "Hi")
);
requestBody.put("messages", messages);
return JSON.toJSONString(requestBody);
}
@Override
public Flux<String> callWorkflow(Map<String, Object> params) {
Constants.baseHttpApiUrl = ALIYUN_API_URL;
ApplicationParam param = ApplicationParam.builder()
.apiKey(ALIYUN_API_KEY)
.appId(ALIYUN_APP_ID)
.flowStreamMode(FlowStreamMode.MESSAGE_FORMAT)
.bizParams(JsonUtils.parse(JSON.toJSONString(params)))
.prompt((String) params.get("audio_prompt"))
.hasThoughts(false)
.incrementalOutput(true)
.build();
Application application = new Application();
try {
Flowable<ApplicationResult> flowableResult = application.streamCall(param);
return Flux.from(flowableResult)
.subscribeOn(Schedulers.boundedElastic())
.map(this::extractWorkflowContent)
.timeout(STREAM_TIMEOUT)
.onErrorResume(TimeoutException.class, e -> {
log.warn("流式输出超时30分钟无数据连接已断开");
return Flux.just("[TIMEOUT]流式输出超时,请检查网络或重新请求");
})
.concatWith(Flux.just("[DONE]"))
.onErrorReturn("错误:流式处理中断,请重试或联系管理员");
} catch (Exception e) {
log.error("调用阿里 SDK 失败", e);
return Flux.just("错误SDK 调用失败:" + e.getMessage());
}
}
private String extractWorkflowContent(ApplicationResult result) {
if (result == null || result.getOutput() == null) {
log.warn("收到空的流式结果");
return "空结果";
}
return result.getOutput().getWorkflowMessage().getMessage().getContent();
}
}