优化
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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为null,fileLink({})]", fileLink);
|
||||
return CommonResult.error(500,"录音文件识别请求失败!");
|
||||
log.error("[videoToCharacters] 提交识别请求失败,taskId为null,fileLink: {}", 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user