Compare commits
3 Commits
a029d57e7d
...
2b9d14cc2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b9d14cc2e | ||
|
|
312e11bc9e | ||
|
|
9aba49a817 |
6
pom.xml
6
pom.xml
@@ -38,6 +38,12 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache HttpClient 5 for RestTemplate -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -179,6 +179,9 @@ public class PasswordUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -163,6 +163,9 @@ public class ServiceManager {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,6 +140,9 @@ public class AliyunConfig {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,8 +135,8 @@ public class CustomerManagementController {
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@Parameter(description = "每页大小", example = "10")
|
||||
@RequestParam(defaultValue = "10") Integer size,
|
||||
@Parameter(description = "客户姓名(模糊查询)")
|
||||
@RequestParam(required = false) String customerName,
|
||||
@Parameter(description = "客户姓名(模糊查询)")@RequestParam(required = false) String customerName,
|
||||
@Parameter(description = "所属人(模糊查询)")@RequestParam(required = false) String salesName,
|
||||
@Parameter(description = "联系方式(模糊查询)")
|
||||
@RequestParam(required = false) String contact,
|
||||
@Parameter(description = "所属门店ID")
|
||||
@@ -150,6 +150,9 @@ public class CustomerManagementController {
|
||||
if (customerName != null && !customerName.trim().isEmpty()) {
|
||||
queryWrapper.like(CustomerManagement::getCustomerName, customerName);
|
||||
}
|
||||
if (salesName != null && !salesName.trim().isEmpty()) {
|
||||
queryWrapper.like(CustomerManagement::getSalesName, salesName);
|
||||
}
|
||||
if (contact != null && !contact.trim().isEmpty()) {
|
||||
queryWrapper.like(CustomerManagement::getContact, contact);
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ public class SiliconFlowTtsController {
|
||||
String fileExtension = originalFilename != null && originalFilename.contains(".")
|
||||
? originalFilename.substring(originalFilename.lastIndexOf("."))
|
||||
: ".mp3";
|
||||
String uniqueFileName = "audio/" + UUID.randomUUID().toString() + fileExtension;
|
||||
String uniqueFileName = "/" + UUID.randomUUID().toString() + fileExtension;
|
||||
|
||||
// 上传文件到MinIO
|
||||
String minioUrl = minIOService.uploadFileWithName(file, uniqueFileName);
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.rj.entity.TtsRequestLog;
|
||||
import com.rj.mapper.TtsRequestLogMapper;
|
||||
import com.rj.service.ITtsRequestLogService;
|
||||
import com.rj.dto.AsrRequest;
|
||||
import com.rj.dto.AsrResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -13,7 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -40,6 +41,7 @@ public class TtsRequestLogController {
|
||||
public ResponseEntity<Map<String, Object>> getTtsRequestLogs(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@Parameter(description = "音乐名称") @RequestParam(required = false) String audioName,
|
||||
@Parameter(description = "状态") @RequestParam(required = false) String status,
|
||||
@Parameter(description = "模型") @RequestParam(required = false) String model,
|
||||
@Parameter(description = "创建人姓名") @RequestParam(required = false) String creatorName,
|
||||
@@ -48,12 +50,15 @@ public class TtsRequestLogController {
|
||||
@Parameter(description = "结束时间") @RequestParam(required = false) String endTime) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
|
||||
try {
|
||||
Page<TtsRequestLog> page = new Page<>(pageNum, pageSize);
|
||||
QueryWrapper<TtsRequestLog> queryWrapper = new QueryWrapper<>();
|
||||
|
||||
// 添加查询条件
|
||||
if (audioName != null && !audioName.trim().isEmpty()) {
|
||||
queryWrapper.like("audio_name", audioName);
|
||||
}
|
||||
if (status != null && !status.trim().isEmpty()) {
|
||||
queryWrapper.eq("status", status);
|
||||
}
|
||||
@@ -149,4 +154,28 @@ public class TtsRequestLogController {
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/speech-to-text")
|
||||
@Operation(summary = "语音转文本", description = "将语音文件转换为文本")
|
||||
public ResponseEntity<AsrResponse> speechToText(
|
||||
@Parameter(description = "语音转文本请求参数") @RequestBody AsrRequest request) {
|
||||
|
||||
try {
|
||||
if (!ttsRequestLogService.isAsrServiceAvailable()) {
|
||||
return ResponseEntity.status(500).body(AsrResponse.error("ASR服务不可用,请检查配置"));
|
||||
}
|
||||
|
||||
AsrResponse response = ttsRequestLogService.speechToText(request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
return ResponseEntity.ok(response);
|
||||
} else {
|
||||
return ResponseEntity.status(500).body(response);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("语音转文本失败: {}", e.getMessage(), e);
|
||||
return ResponseEntity.status(500).body(AsrResponse.error("语音转文本失败: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,6 +401,9 @@ public class MenuController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -371,6 +371,9 @@ public class RoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -377,6 +377,9 @@ public class UserRoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
90
src/main/java/com/rj/dto/AsrRequest.java
Normal file
90
src/main/java/com/rj/dto/AsrRequest.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.rj.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 语音转文本请求DTO
|
||||
*
|
||||
* @author rj
|
||||
* @date 2025-01-02
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "语音转文本请求参数")
|
||||
public class AsrRequest {
|
||||
|
||||
/**
|
||||
* 音频文件URL
|
||||
*/
|
||||
@NotBlank(message = "音频文件URL不能为空")
|
||||
@Schema(description = "音频文件URL", example = "https://example.com/audio.mp3")
|
||||
private String audioUrl;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@Schema(description = "ASR模型名称", example = "paraformer-v2")
|
||||
private String model = "paraformer-v2";
|
||||
|
||||
/**
|
||||
* 音频格式
|
||||
*/
|
||||
@Schema(description = "音频格式", example = "wav")
|
||||
private String format = "wav";
|
||||
|
||||
/**
|
||||
* 采样率
|
||||
*/
|
||||
@Schema(description = "采样率", example = "16000")
|
||||
private Integer sampleRate = 16000;
|
||||
|
||||
/**
|
||||
* 是否启用标点符号
|
||||
*/
|
||||
@Schema(description = "是否启用标点符号", example = "true")
|
||||
private Boolean enablePunctuation = true;
|
||||
|
||||
/**
|
||||
* 是否启用数字转换
|
||||
*/
|
||||
@Schema(description = "是否启用数字转换", example = "true")
|
||||
private Boolean enableNumberConversion = true;
|
||||
|
||||
/**
|
||||
* 是否启用说话人分离
|
||||
*/
|
||||
@Schema(description = "是否启用说话人分离", example = "false")
|
||||
private Boolean enableSpeakerDiarization = false;
|
||||
|
||||
/**
|
||||
* 说话人数量
|
||||
*/
|
||||
@Schema(description = "说话人数量", example = "1")
|
||||
private Integer speakerCount = 1;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@Schema(description = "创建人姓名", example = "张三")
|
||||
private String creatorName;
|
||||
|
||||
/**
|
||||
* 创建人电话
|
||||
*/
|
||||
@Schema(description = "创建人电话", example = "13800138000")
|
||||
private String creatorPhone;
|
||||
|
||||
/**
|
||||
* 音频名称
|
||||
*/
|
||||
@Schema(description = "音频名称", example = "test_audio")
|
||||
private String audioName;
|
||||
|
||||
/**
|
||||
* 关联的TTS请求日志ID
|
||||
*/
|
||||
@Schema(description = "关联的TTS请求日志ID", example = "12345678-1234-1234-1234-123456789012")
|
||||
private String id;
|
||||
}
|
||||
160
src/main/java/com/rj/dto/AsrResponse.java
Normal file
160
src/main/java/com/rj/dto/AsrResponse.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package com.rj.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 语音转文本响应DTO
|
||||
*
|
||||
* @author rj
|
||||
* @date 2025-01-02
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "语音转文本响应结果")
|
||||
public class AsrResponse {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
@Schema(description = "是否成功")
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
@Schema(description = "响应消息")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 识别结果文本
|
||||
*/
|
||||
@Schema(description = "识别结果文本")
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* 识别结果列表(包含时间戳和说话人信息)
|
||||
*/
|
||||
@Schema(description = "识别结果列表")
|
||||
private List<AsrResult> results;
|
||||
|
||||
/**
|
||||
* 音频时长(秒)
|
||||
*/
|
||||
@Schema(description = "音频时长(秒)")
|
||||
private Double duration;
|
||||
|
||||
/**
|
||||
* 处理时间(毫秒)
|
||||
*/
|
||||
@Schema(description = "处理时间(毫秒)")
|
||||
private Long processingTimeMs;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
@Schema(description = "任务ID")
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 请求ID
|
||||
*/
|
||||
@Schema(description = "请求ID")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 错误代码
|
||||
*/
|
||||
@Schema(description = "错误代码")
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 识别结果详情
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "识别结果详情")
|
||||
public static class AsrResult {
|
||||
|
||||
/**
|
||||
* 开始时间(秒)
|
||||
*/
|
||||
@Schema(description = "开始时间(秒)")
|
||||
private Double startTime;
|
||||
|
||||
/**
|
||||
* 结束时间(秒)
|
||||
*/
|
||||
@Schema(description = "结束时间(秒)")
|
||||
private Double endTime;
|
||||
|
||||
/**
|
||||
* 识别文本
|
||||
*/
|
||||
@Schema(description = "识别文本")
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* 说话人ID
|
||||
*/
|
||||
@Schema(description = "说话人ID")
|
||||
private String speakerId;
|
||||
|
||||
/**
|
||||
* 置信度
|
||||
*/
|
||||
@Schema(description = "置信度")
|
||||
private Double confidence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
public static AsrResponse success(String text, List<AsrResult> results, Double duration,
|
||||
Long processingTimeMs, String taskId, String requestId) {
|
||||
AsrResponse response = new AsrResponse();
|
||||
response.setSuccess(true);
|
||||
response.setMessage("语音识别成功");
|
||||
response.setText(text);
|
||||
response.setResults(results);
|
||||
response.setDuration(duration);
|
||||
response.setProcessingTimeMs(processingTimeMs);
|
||||
response.setTaskId(taskId);
|
||||
response.setRequestId(requestId);
|
||||
response.setCreateTime(LocalDateTime.now());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应
|
||||
*/
|
||||
public static AsrResponse error(String message) {
|
||||
return error(message, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应
|
||||
*/
|
||||
public static AsrResponse error(String message, String errorCode, String errorMessage) {
|
||||
AsrResponse response = new AsrResponse();
|
||||
response.setSuccess(false);
|
||||
response.setMessage(message);
|
||||
response.setErrorCode(errorCode);
|
||||
response.setErrorMessage(errorMessage);
|
||||
response.setCreateTime(LocalDateTime.now());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,9 @@ public class DifyWorkflowResponseDto {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,5 +73,9 @@ public class CustomerManagement implements Serializable {
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "备注")
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.rj.service;
|
||||
|
||||
import com.rj.entity.TtsRequestLog;
|
||||
import com.rj.utils.MinIOUrlGenerator;
|
||||
import com.rj.dto.AsrRequest;
|
||||
import com.rj.dto.AsrResponse;
|
||||
|
||||
/**
|
||||
* TTS请求日志服务接口
|
||||
@@ -41,4 +43,19 @@ public interface ITtsRequestLogService {
|
||||
* @return 是否设置成功
|
||||
*/
|
||||
boolean setShortUrlByAudioName(String audioName);
|
||||
|
||||
/**
|
||||
* 语音转文本
|
||||
*
|
||||
* @param request ASR请求
|
||||
* @return ASR响应
|
||||
*/
|
||||
AsrResponse speechToText(AsrRequest request);
|
||||
|
||||
/**
|
||||
* 检查ASR服务是否可用
|
||||
*
|
||||
* @return 是否可用
|
||||
*/
|
||||
boolean isAsrServiceAvailable();
|
||||
}
|
||||
|
||||
@@ -107,6 +107,9 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -142,6 +142,9 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package com.rj.service.impl;
|
||||
|
||||
import com.alibaba.dashscope.audio.asr.transcription.*;
|
||||
import com.rj.entity.TtsRequestLog;
|
||||
import com.rj.mapper.TtsRequestLogMapper;
|
||||
import com.rj.service.ITtsRequestLogService;
|
||||
import com.rj.utils.MinIOUrlGenerator;
|
||||
import com.rj.dto.AsrRequest;
|
||||
import com.rj.dto.AsrResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TTS请求日志服务实现类
|
||||
@@ -27,6 +36,15 @@ public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
|
||||
@Autowired
|
||||
private MinIOUrlGenerator urlGenerator;
|
||||
|
||||
@Value("${dashscope.api.key}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${asr.enabled:true}")
|
||||
private boolean asrEnabled;
|
||||
|
||||
@Value("${asr.default-model:paraformer-v2}")
|
||||
private String defaultAsrModel;
|
||||
|
||||
@Override
|
||||
public boolean saveTtsRequestLog(TtsRequestLog requestLog) {
|
||||
try {
|
||||
@@ -52,7 +70,7 @@ public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
|
||||
@Override
|
||||
public boolean setShortUrlByAudioName(MinIOUrlGenerator.UrlInfo urlInfo ) {
|
||||
try {
|
||||
if (urlInfo == null || urlInfo.getFileName().trim().isEmpty()) {
|
||||
if (urlInfo == null || urlInfo.getFileName() == null || urlInfo.getFileName().trim().isEmpty()) {
|
||||
log.error("音频名称不能为空");
|
||||
return false;
|
||||
}
|
||||
@@ -92,4 +110,291 @@ public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
|
||||
public boolean setShortUrlByAudioName(String audioName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsrResponse speechToText(AsrRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
if (!isAsrServiceAvailable()) {
|
||||
return AsrResponse.error("ASR服务不可用,请检查配置");
|
||||
}
|
||||
|
||||
if (request.getAudioUrl() == null || request.getAudioUrl().trim().isEmpty()) {
|
||||
return AsrResponse.error("音频文件URL不能为空");
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (request.getModel() == null || request.getModel().trim().isEmpty()) {
|
||||
request.setModel(defaultAsrModel);
|
||||
}
|
||||
if (request.getFormat() == null || request.getFormat().trim().isEmpty()) {
|
||||
request.setFormat("wav");
|
||||
}
|
||||
if (request.getSampleRate() == null) {
|
||||
request.setSampleRate(16000);
|
||||
}
|
||||
if (request.getEnablePunctuation() == null) {
|
||||
request.setEnablePunctuation(true);
|
||||
}
|
||||
if (request.getEnableNumberConversion() == null) {
|
||||
request.setEnableNumberConversion(true);
|
||||
}
|
||||
if (request.getEnableSpeakerDiarization() == null) {
|
||||
request.setEnableSpeakerDiarization(false);
|
||||
}
|
||||
if (request.getSpeakerCount() == null) {
|
||||
request.setSpeakerCount(1);
|
||||
}
|
||||
|
||||
log.info("开始语音识别 - 音频URL: {}, 模型: {}, 格式: {}, 采样率: {}",
|
||||
request.getAudioUrl(), request.getModel(), request.getFormat(), request.getSampleRate());
|
||||
|
||||
// 创建转写请求参数
|
||||
TranscriptionParam param = TranscriptionParam.builder()
|
||||
.apiKey(apiKey)
|
||||
.model(request.getModel())
|
||||
.fileUrls(Arrays.asList(request.getAudioUrl()))
|
||||
.build();
|
||||
|
||||
// 创建转写对象
|
||||
Transcription transcription = new Transcription();
|
||||
|
||||
// 提交转写请求
|
||||
TranscriptionResult result = transcription.asyncCall(param);
|
||||
String taskId = result.getTaskId();
|
||||
String requestId = result.getRequestId();
|
||||
|
||||
log.info("ASR任务已提交 - TaskId: {}, RequestId: {}", taskId, requestId);
|
||||
|
||||
// 等待任务完成
|
||||
TranscriptionQueryParam queryParam = TranscriptionQueryParam.FromTranscriptionParam(param, taskId);
|
||||
result = transcription.wait(queryParam);
|
||||
log.info("语音识别完成 - result: {} ", result);
|
||||
long processingTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (result.getResults() != null && !result.getResults().isEmpty()) {
|
||||
// 解析识别结果 - 从results中获取transcriptionUrl并下载识别结果
|
||||
String fullText = "";
|
||||
Double duration = 0.0;
|
||||
List<AsrResponse.AsrResult> asrResults = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 从usage中获取时长信息
|
||||
if (result.getUsage() != null) {
|
||||
// 从usage对象中获取duration,usage通常是一个JsonObject
|
||||
try {
|
||||
String usageStr = result.getUsage().toString();
|
||||
if (usageStr.contains("\"duration\"")) {
|
||||
int durationStart = usageStr.indexOf("\"duration\":") + 11;
|
||||
int durationEnd = usageStr.indexOf(",", durationStart);
|
||||
if (durationEnd == -1) {
|
||||
durationEnd = usageStr.indexOf("}", durationStart);
|
||||
}
|
||||
if (durationStart > 10 && durationEnd > durationStart) {
|
||||
duration = Double.parseDouble(usageStr.substring(durationStart, durationEnd).trim());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析usage中的duration失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历所有结果,获取transcriptionUrl并下载识别文本
|
||||
for (TranscriptionTaskResult taskResult : result.getResults()) {
|
||||
if (taskResult.getTranscriptionUrl() != null) {
|
||||
try {
|
||||
// 从transcriptionUrl下载识别结果
|
||||
String transcriptionResult = downloadTranscriptionResult(taskResult.getTranscriptionUrl());
|
||||
log.info("transcriptionResult: {}" , transcriptionResult);
|
||||
|
||||
if (transcriptionResult != null && !transcriptionResult.trim().isEmpty()) {
|
||||
// 解析transcription结果JSON
|
||||
String extractedText = parseTranscriptionResult(transcriptionResult);
|
||||
if (extractedText != null && !extractedText.trim().isEmpty()) {
|
||||
fullText += extractedText;
|
||||
|
||||
// 输出识别文本到控制台
|
||||
System.out.println("=== 语音识别结果 ===");
|
||||
System.out.println("任务ID: " + taskId);
|
||||
System.out.println("识别文本: " + extractedText);
|
||||
System.out.println("==================");
|
||||
|
||||
// 创建结果对象
|
||||
AsrResponse.AsrResult asrResult = new AsrResponse.AsrResult();
|
||||
asrResult.setText(extractedText);
|
||||
asrResult.setStartTime(0.0);
|
||||
asrResult.setEndTime(duration);
|
||||
asrResults.add(asrResult);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("下载transcription结果失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("解析ASR结果时出错: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (fullText.trim().isEmpty()) {
|
||||
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
|
||||
return AsrResponse.error("语音识别结果为空");
|
||||
}
|
||||
|
||||
log.info("语音识别完成 - 识别文本长度: {}, 处理时间: {}ms", fullText.length(), processingTime);
|
||||
|
||||
// 如果提供了TTS日志ID,则保存识别文本到数据库
|
||||
if (request.getId() != null && !request.getId().trim().isEmpty()) {
|
||||
saveRecognizedTextToDatabase(request.getId(), fullText);
|
||||
}
|
||||
|
||||
return AsrResponse.success(fullText, asrResults, duration, processingTime, taskId, requestId);
|
||||
} else {
|
||||
log.warn("ASR识别结果为空 - TaskId: {}", taskId);
|
||||
return AsrResponse.error("语音识别结果为空");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("语音识别失败: {}", e.getMessage(), e);
|
||||
return AsrResponse.error("语音识别失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsrServiceAvailable() {
|
||||
return asrEnabled && apiKey != null && !apiKey.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载transcription结果
|
||||
*/
|
||||
private String downloadTranscriptionResult(String transcriptionUrl) {
|
||||
try {
|
||||
log.info("开始下载transcription结果,URL: {}", transcriptionUrl);
|
||||
|
||||
// 使用简单的URL连接下载transcription结果,避免HttpClient依赖问题
|
||||
java.net.URL url = new java.net.URL(transcriptionUrl);
|
||||
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
|
||||
|
||||
// 设置请求头
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
connection.setRequestProperty("Accept", "application/json");
|
||||
connection.setConnectTimeout(30000); // 30秒连接超时
|
||||
connection.setReadTimeout(60000); // 60秒读取超时
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
// 读取响应内容
|
||||
StringBuilder result = new StringBuilder();
|
||||
try (java.io.BufferedReader reader = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(connection.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
String responseBody = result.toString().trim();
|
||||
log.info("成功下载transcription结果,长度: {} 字符", responseBody.length());
|
||||
return responseBody;
|
||||
} else {
|
||||
log.warn("下载transcription结果失败,状态码: {}", responseCode);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("下载transcription结果异常: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析transcription结果JSON,提取识别文本
|
||||
*/
|
||||
private String parseTranscriptionResult(String transcriptionJson) {
|
||||
try {
|
||||
// 使用Jackson解析JSON
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode rootNode = mapper.readTree(transcriptionJson);
|
||||
|
||||
log.info("开始解析transcription JSON");
|
||||
|
||||
// 根据阿里云ASR返回的JSON结构解析
|
||||
// 阿里云返回的结构: {"transcripts": [{"text": "识别的文本", "sentences": [...]}]}
|
||||
JsonNode transcriptsNode = rootNode.get("transcripts");
|
||||
if (transcriptsNode != null && transcriptsNode.isArray() && transcriptsNode.size() > 0) {
|
||||
JsonNode firstTranscript = transcriptsNode.get(0);
|
||||
JsonNode textNode = firstTranscript.get("text");
|
||||
if (textNode != null && !textNode.isNull()) {
|
||||
String text = textNode.asText();
|
||||
log.info("成功提取识别文本: {}", text);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有transcripts字段,尝试其他可能的字段结构
|
||||
JsonNode resultsNode = rootNode.get("results");
|
||||
if (resultsNode != null && resultsNode.isArray()) {
|
||||
StringBuilder textBuilder = new StringBuilder();
|
||||
for (JsonNode resultNode : resultsNode) {
|
||||
JsonNode textNode = resultNode.get("text");
|
||||
if (textNode != null && !textNode.isNull()) {
|
||||
String text = textNode.asText();
|
||||
textBuilder.append(text).append(" ");
|
||||
log.info("提取到文本片段: {}", text);
|
||||
}
|
||||
}
|
||||
String fullText = textBuilder.toString().trim();
|
||||
if (!fullText.isEmpty()) {
|
||||
log.info("完整识别文本: {}", fullText);
|
||||
return fullText;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有results字段,尝试直接获取text字段
|
||||
JsonNode textNode = rootNode.get("text");
|
||||
if (textNode != null && !textNode.isNull()) {
|
||||
String text = textNode.asText();
|
||||
log.info("直接获取文本: {}", text);
|
||||
return text;
|
||||
}
|
||||
|
||||
// 如果都没有,记录JSON结构用于调试
|
||||
log.warn("未找到预期的文本字段,JSON结构: {}", rootNode.toString());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("解析transcription结果JSON失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存识别文本到数据库
|
||||
*/
|
||||
private void saveRecognizedTextToDatabase(String ttsLogId, String recognizedText) {
|
||||
try {
|
||||
// 根据ID查询TTS请求日志
|
||||
TtsRequestLog ttsLog = ttsRequestLogMapper.selectById(ttsLogId);
|
||||
if (ttsLog != null) {
|
||||
// 将识别文本保存到inputText字段
|
||||
ttsLog.setInputText(recognizedText);
|
||||
ttsLog.setInputLength(recognizedText.length());
|
||||
ttsLog.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存到数据库
|
||||
int updateResult = ttsRequestLogMapper.updateById(ttsLog);
|
||||
if (updateResult > 0) {
|
||||
log.info("成功保存识别文本到数据库,TTS日志ID: {}, 文本长度: {}", ttsLogId, recognizedText.length());
|
||||
} else {
|
||||
log.warn("保存识别文本到数据库失败,TTS日志ID: {}", ttsLogId);
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到对应的TTS请求日志,ID: {}", ttsLogId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存识别文本到数据库异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -114,6 +114,9 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -114,6 +114,9 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ spring:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
spring.application.name=Langchain4j-heima20250803
|
||||
spring.application.name=Langchain4j-cst20250803
|
||||
|
||||
@@ -125,6 +125,27 @@ aliyun:
|
||||
# 是否启用阿里云服务
|
||||
enabled: true
|
||||
|
||||
# ASR语音识别配置
|
||||
asr:
|
||||
# 是否启用ASR服务
|
||||
enabled: true
|
||||
# 默认ASR模型
|
||||
default-model: paraformer-v2
|
||||
# 请求超时时间(毫秒)
|
||||
timeout: 60000
|
||||
# 是否启用标点符号
|
||||
enable-punctuation: true
|
||||
# 是否启用数字转换
|
||||
enable-number-conversion: true
|
||||
# 是否启用说话人分离
|
||||
enable-speaker-diarization: false
|
||||
# 默认说话人数量
|
||||
default-speaker-count: 1
|
||||
# 默认音频格式
|
||||
default-format: wav
|
||||
# 默认采样率
|
||||
default-sample-rate: 16000
|
||||
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
||||
@@ -100,6 +100,9 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
<result column="recording_count" property="recordingCount" />
|
||||
<result column="intended_model" property="intendedModel" />
|
||||
<result column="info_card" property="infoCard" />
|
||||
<result column="remark" property="remark" />
|
||||
<result column="create_time" property="createTime" />
|
||||
<result column="update_time" property="updateTime" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, customer_name, contact, dealership_id, dealership_name, sales_id, sales_name, recording_count, intended_model, info_card, create_time, update_time
|
||||
id, customer_name, contact, dealership_id, dealership_name, sales_id, sales_name, recording_count, intended_model, info_card, remark, create_time, update_time
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -299,6 +299,9 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ CREATE TABLE `customer_management` (
|
||||
`recording_count` int NULL DEFAULT 0 COMMENT '录音条数',
|
||||
`intended_model` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '意向车型',
|
||||
`info_card` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '信息卡',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.rj;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class Langchain4jHeima20250803ApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -237,6 +237,9 @@ public class FaceDetectImageCountTest {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.rj.dto.AsrRequest;
|
||||
import com.rj.dto.AsrResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* TtsRequestLogService ASR功能测试类
|
||||
*
|
||||
* @author rj
|
||||
* @date 2025-01-02
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class TtsRequestLogServiceAsrTest {
|
||||
|
||||
@Autowired
|
||||
private ITtsRequestLogService ttsRequestLogService;
|
||||
|
||||
@Test
|
||||
public void testSpeechToText() {
|
||||
// 创建测试请求
|
||||
AsrRequest request = new AsrRequest();
|
||||
request.setAudioUrl("https://api.huayang-star.com/api/audio/1957424645898579969_a2db5d76-b52c-4289-9c7e-ed55d7f15e42.mp3");
|
||||
request.setModel("paraformer-v2");
|
||||
request.setFormat("mp3");
|
||||
request.setSampleRate(16000);
|
||||
request.setEnablePunctuation(true);
|
||||
request.setEnableNumberConversion(true);
|
||||
request.setCreatorName("测试用户");
|
||||
request.setCreatorPhone("13800138000");
|
||||
request.setAudioName("测试音频");
|
||||
|
||||
// 执行语音识别
|
||||
AsrResponse response = ttsRequestLogService.speechToText(request);
|
||||
|
||||
// 打印结果
|
||||
System.out.println("识别结果: " + response.isSuccess());
|
||||
System.out.println("识别文本: " + response.getText());
|
||||
System.out.println("处理时间: " + response.getProcessingTimeMs() + "ms");
|
||||
System.out.println("任务ID: " + response.getTaskId());
|
||||
System.out.println("请求ID: " + response.getRequestId());
|
||||
|
||||
if (response.getResults() != null && !response.getResults().isEmpty()) {
|
||||
System.out.println("结果详情:");
|
||||
for (AsrResponse.AsrResult result : response.getResults()) {
|
||||
System.out.println(" 文本: " + result.getText());
|
||||
System.out.println(" 开始时间: " + result.getStartTime());
|
||||
System.out.println(" 结束时间: " + result.getEndTime());
|
||||
System.out.println(" 说话人ID: " + result.getSpeakerId());
|
||||
System.out.println(" 置信度: " + result.getConfidence());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsrServiceAvailable() {
|
||||
boolean available = ttsRequestLogService.isAsrServiceAvailable();
|
||||
System.out.println("ASR服务是否可用: " + available);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +178,9 @@ public class TtsRequestLogShortUrlTest {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -156,6 +156,9 @@ public class VideoSynthesisTempUrlTest {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ public class VideoSynthesisVideoNameTest {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user