Default Changelist

This commit is contained in:
wing
2025-10-06 13:34:12 +08:00
parent 14e1778eb1
commit 4bdc6337dd
10 changed files with 444 additions and 17 deletions

View File

@@ -25,6 +25,26 @@
</properties>
<dependencies>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.nls</groupId>
<artifactId>nls-sdk-transcriber</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.nls</groupId>
<artifactId>nls-sdk-common</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system</artifactId>

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.ai.controller.admin.tikhup;
import cn.iocoder.yudao.module.ai.service.tikhup.TikHupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "TikHup - TikTok数据管理", description = "TikHup平台TikTok数据获取相关接口")
@RestController
@RequestMapping("/ai/tikHup")
@RequiredArgsConstructor
public class TikHupController {
private final TikHupService tikHupService;
@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("/videoToCharacters")
@Operation(summary = "音频转文字", description = "音频转文字接口")
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) {
List<String> fileLinkList = (List<String>) fileLinkMap.get("fileLinkList");
return tikHupService.videoToCharacters2(fileLinkList);
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.ai.controller.admin.tikhup.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "TikToken - TikToken管理 Request VO")
@TableName("tik_token")
@Data
public class TikTokenVO {
@Schema(
description = "TikHup平台API地址",
requiredMode = Schema.RequiredMode.REQUIRED,
example = "https://api.tikhub.io/api/v1/xiaohongshu/app/get_user_info",
title = "平台API地址"
)
@TableField(value = "platform_url")
private String platformUrl;
@Schema(
description = "TikHup平台访问Token用于API认证",
requiredMode = Schema.RequiredMode.REQUIRED,
example = "y15aJOgphU+dik4QGaCW3CL2bR7eQ6ZA2Tz3tUe8c6ycoW27OX+zRTO6Ow==",
title = "平台访问Token"
)
@TableField(value = "platform_token")
private String platformToken;
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.ai.dal.mysql.tikhup;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.ai.controller.admin.tikhup.vo.TikTokenVO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TikHupMapper extends BaseMapperX<TikTokenVO> {
default TikTokenVO getPlatformToken(String platformUrl) {
return selectOne("platform_url",platformUrl);
}
}

View File

@@ -0,0 +1,178 @@
package cn.iocoder.yudao.module.ai.service.tikhup;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
public class TikFileTransCharacters {
// 地域ID常量固定值。
public static final String REGIONID = "cn-shanghai";
public static final String ENDPOINTNAME = "cn-shanghai";
public static final String PRODUCT = "nls-filetrans";
public static final String DOMAIN = "filetrans.cn-shanghai.aliyuncs.com";
public static final String API_VERSION = "2018-08-17"; // 中国站版本
// public static final String API_VERSION = "2019-08-23"; // 国际站版本
public static final String POST_REQUEST_ACTION = "SubmitTask";
public static final String GET_REQUEST_ACTION = "GetTaskResult";
// 请求参数
public static final String KEY_APP_KEY = "appkey";
public static final String KEY_FILE_LINK = "file_link";
public static final String KEY_VERSION = "version";
public static final String KEY_ENABLE_WORDS = "enable_words";
// 响应参数
public static final String KEY_TASK = "Task";
public static final String KEY_TASK_ID = "TaskId";
public static final String KEY_STATUS_TEXT = "StatusText";
public static final String KEY_RESULT = "Result";
// 状态值
public static final String STATUS_SUCCESS = "SUCCESS";
private static final String STATUS_RUNNING = "RUNNING";
private static final String STATUS_QUEUEING = "QUEUEING";
// 阿里云鉴权client
IAcsClient client;
public TikFileTransCharacters(String accessKeyId, String accessKeySecret) {
// 设置endpoint
try {
DefaultProfile.addEndpoint(ENDPOINTNAME, REGIONID, PRODUCT, DOMAIN);
} catch (ClientException e) {
e.printStackTrace();
}
// 创建DefaultAcsClient实例并初始化
DefaultProfile profile = DefaultProfile.getProfile(REGIONID, accessKeyId, accessKeySecret);
this.client = new DefaultAcsClient(profile);
}
public String submitFileTransRequest(String appKey, String fileLink) {
/**
* 1. 创建CommonRequest设置请求参数。
*/
CommonRequest postRequest = new CommonRequest();
// 设置域名
postRequest.setDomain(DOMAIN);
// 设置API的版本号格式为YYYY-MM-DD。
postRequest.setVersion(API_VERSION);
// 设置action
postRequest.setAction(POST_REQUEST_ACTION);
// 设置产品名称
postRequest.setProduct(PRODUCT);
/**
* 2. 设置录音文件识别请求参数以JSON字符串的格式设置到请求Body中。
*/
JSONObject taskObject = new JSONObject();
// 设置appkey
taskObject.put(KEY_APP_KEY, appKey);
// 设置音频文件访问链接
taskObject.put(KEY_FILE_LINK, fileLink);
// 新接入请使用4.0版本已接入默认2.0)如需维持现状,请注释掉该参数设置。
taskObject.put(KEY_VERSION, "4.0");
// 设置是否输出词信息默认为false开启时需要设置version为4.0及以上。
taskObject.put(KEY_ENABLE_WORDS, true);
String task = taskObject.toJSONString();
System.out.println(task);
// 设置以上JSON字符串为Body参数。
postRequest.putBodyParameter(KEY_TASK, task);
// 设置为POST方式的请求。
postRequest.setMethod(MethodType.POST);
// postRequest.setHttpContentType(FormatType.JSON); //当aliyun-java-sdk-core 版本为4.6.0及以上时,请取消该行注释
/**
* 3. 提交录音文件识别请求获取录音文件识别请求任务的ID以供识别结果查询使用。
*/
String taskId = null;
try {
CommonResponse postResponse = client.getCommonResponse(postRequest);
System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());
if (postResponse.getHttpStatus() == 200) {
JSONObject result = JSONObject.parseObject(postResponse.getData());
String statusText = result.getString(KEY_STATUS_TEXT);
if (STATUS_SUCCESS.equals(statusText)) {
taskId = result.getString(KEY_TASK_ID);
}
}
} catch (ClientException e) {
e.printStackTrace();
}
return taskId;
}
public String getFileTransResult(String taskId) {
/**
* 1. 创建CommonRequest设置任务ID。
*/
CommonRequest getRequest = new CommonRequest();
// 设置域名
getRequest.setDomain(DOMAIN);
// 设置API版本
getRequest.setVersion(API_VERSION);
// 设置action
getRequest.setAction(GET_REQUEST_ACTION);
// 设置产品名称
getRequest.setProduct(PRODUCT);
// 设置任务ID为查询参数
getRequest.putQueryParameter(KEY_TASK_ID, taskId);
// 设置为GET方式的请求
getRequest.setMethod(MethodType.GET);
/**
* 2. 提交录音文件识别结果查询请求
* 以轮询的方式进行识别结果的查询直到服务端返回的状态描述为“SUCCESS”或错误描述则结束轮询。
*/
String result = null;
while (true) {
try {
CommonResponse getResponse = client.getCommonResponse(getRequest);
System.err.println("识别查询结果:" + getResponse.getData());
if (getResponse.getHttpStatus() != 200) {
break;
}
JSONObject rootObj = JSONObject.parseObject(getResponse.getData());
String statusText = rootObj.getString(KEY_STATUS_TEXT);
if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {
// 继续轮询,注意设置轮询时间间隔。
Thread.sleep(10000);
}
else {
// 状态信息为成功,返回识别结果;状态信息为异常,返回空。
if (STATUS_SUCCESS.equals(statusText)) {
result = rootObj.getString(KEY_RESULT);
// 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空。
if(result == null) {
result = "";
}
}
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
public static void main(String args[]) throws Exception {
final String accessKeyId = System.getenv().get("ALIYUN_AK_ID");
final String accessKeySecret = System.getenv().get("ALIYUN_AK_SECRET");
final String appKey = System.getenv().get("NLS_APP_KEY");
String fileLink = "https://gw.alipayobjects.com/os/bmw-prod/0574ee2e-f494-45a5-820f-63aee583045a.wav";
TikFileTransCharacters demo = new TikFileTransCharacters(accessKeyId, accessKeySecret);
// 第一步提交录音文件识别请求获取任务ID用于后续的识别结果轮询。
String taskId = demo.submitFileTransRequest(appKey, fileLink);
if (taskId != null) {
System.out.println("录音文件识别请求成功task_id: " + taskId);
}
else {
System.out.println("录音文件识别请求失败!");
return;
}
// 第二步根据任务ID轮询识别结果。
String result = demo.getFileTransResult(taskId);
if (result != null) {
System.out.println("录音文件识别结果查询成功:" + result);
}
else {
System.out.println("录音文件识别结果查询失败!");
}
}
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.ai.service.tikhup;
import java.util.List;
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);
/**
* 音频转文字
* @param downloadUrl 下载地址
* @return 语音文字
*/
Object videoToCharacters(String downloadUrl);
/**
* 批量音频转文字
* @param fileLinkList 音频地址
* @return 语音文字
*/
Object videoToCharacters2(List<String> fileLinkList);
}

View File

@@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.ai.service.tikhup;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.ai.controller.admin.tikhup.vo.TikTokenVO;
import cn.iocoder.yudao.module.ai.dal.mysql.tikhup.TikHupMapper;
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.fastjson.JSON;
import com.google.gson.GsonBuilder;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class TikHupServiceImpl implements TikHupService{
private final TikHupMapper tikHupMapper;
@Override
public Object fetch_user_post_videos(String type,String sec_user_id, int max_cursor, int count){
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 = tikHupMapper.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){
Long userId = SecurityFrameworkUtils.getLoginUser().getId();
return JSON.parseObject(response.getBody());
}
}catch (Exception e){
log.error("fetch_user_post_videos接口调用异常");
}
return new HashMap<>();
}
private final String appKey = "sldJ4XSpYp3rKALZ ";
private final String accessKeyId = "LTAI5tPV9Ag3csf41GZjaLTA";
private final String accessKeySecret = "kDqlGeJTKw6tJtFYiaY8vQTFuVIQDs";
@Override
public Object videoToCharacters(String fileLink){
TikFileTransCharacters tikFileTransCharacters = new TikFileTransCharacters(accessKeyId, accessKeySecret);
// 第一步提交录音文件识别请求获取任务ID用于后续的识别结果轮询。
String taskId = tikFileTransCharacters.submitFileTransRequest(appKey, fileLink);
if (taskId == null) {
return CommonResult.error(500,"录音文件识别请求失败!");
}
// 第二步根据任务ID轮询识别结果。
String transResult = tikFileTransCharacters.getFileTransResult(taskId);
if (transResult == null) {
return CommonResult.error(501,"录音文件识别请求失败!");
}
return CommonResult.success(transResult);
}
private final String apiKey = "sk-10c746f8cb8640738f8d6b71af699003";
@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();
try {
Transcription transcription = new Transcription();
// 提交转写请求
TranscriptionResult result = transcription.asyncCall(param);
log.info("RequestId: {}" ,result.getRequestId());
// 阻塞等待任务完成并获取结果
result = transcription.wait(
TranscriptionQueryParam.FromTranscriptionParam(param, result.getTaskId()));
return CommonResult.success(new GsonBuilder().setPrettyPrinting().create().toJson(result.getOutput()));
} catch (Exception e) {
log.error(e.getMessage());
return CommonResult.error(500,"录音文件识别请求失败!");
}
}
}