客户管理功能调整, 音频模型调整

This commit is contained in:
spllzh
2025-10-21 17:46:24 +08:00
parent 9aba49a817
commit 312e11bc9e
30 changed files with 538 additions and 47 deletions

View File

@@ -183,5 +183,6 @@ public class PasswordUtil {

View File

@@ -167,5 +167,6 @@ public class ServiceManager {

View File

@@ -144,5 +144,6 @@ public class AliyunConfig {

View File

@@ -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);
}

View File

@@ -405,5 +405,6 @@ public class MenuController {

View File

@@ -375,5 +375,6 @@ public class RoleController {

View File

@@ -381,5 +381,6 @@ public class UserRoleController {

View 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;
}

View 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;
}
}

View File

@@ -147,5 +147,6 @@ public class DifyWorkflowResponseDto {

View File

@@ -73,5 +73,9 @@ public class CustomerManagement implements Serializable {
@TableField("update_time")
private LocalDateTime updateTime;
@Schema(description = "备注")
@TableField("remark")
private String remark;
}

View File

@@ -90,5 +90,6 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil

View File

@@ -111,5 +111,6 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil

View File

@@ -146,5 +146,6 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf

View File

@@ -12,6 +12,8 @@ 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;
@@ -171,54 +173,83 @@ public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
log.info("语音识别完成 - result: {} ", result);
long processingTime = System.currentTimeMillis() - startTime;
if (result.getOutput() != null) {
// 解析识别结果 - 从JSON中提取文本
if (result.getResults() != null && !result.getResults().isEmpty()) {
// 解析识别结果 - 从results中获取transcriptionUrl并下载识别结果
String fullText = "";
Double duration = 0.0;
List<AsrResponse.AsrResult> asrResults = new ArrayList<>();
try {
// 从输出中提取文本和时长信息
if (result.getOutput().toString().contains("\"text\"")) {
// 简单解析JSON获取文本
String outputStr = result.getOutput().toString();
int textStart = outputStr.indexOf("\"text\":\"") + 8;
int textEnd = outputStr.indexOf("\"", textStart);
if (textStart > 7 && textEnd > textStart) {
fullText = outputStr.substring(textStart, textEnd);
// 从usage中获取时长信息
if (result.getUsage() != null) {
// 从usage对象中获取durationusage通常是一个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());
}
}
// 提取时长信息
if (result.getOutput().toString().contains("\"duration\"")) {
String outputStr = result.getOutput().toString();
int durationStart = outputStr.indexOf("\"duration\":") + 11;
int durationEnd = outputStr.indexOf(",", durationStart);
if (durationEnd == -1) {
durationEnd = outputStr.indexOf("}", durationStart);
}
if (durationStart > 10 && durationEnd > durationStart) {
// 遍历所有结果获取transcriptionUrl并下载识别文本
for (TranscriptionTaskResult taskResult : result.getResults()) {
if (taskResult.getTranscriptionUrl() != null) {
try {
duration = Double.parseDouble(outputStr.substring(durationStart, durationEnd).trim());
} catch (NumberFormatException e) {
duration = 0.0;
// 从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());
}
List<AsrResponse.AsrResult> asrResults = new ArrayList<>();
// 创建简单的结果对象
AsrResponse.AsrResult asrResult = new AsrResponse.AsrResult();
asrResult.setText(fullText);
asrResult.setStartTime(0.0);
asrResult.setEndTime(duration);
asrResults.add(asrResult);
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);
@@ -235,4 +266,135 @@ public class TtsRequestLogServiceImpl implements ITtsRequestLogService {
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);
}
}
}

View File

@@ -118,5 +118,6 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM

View File

@@ -118,5 +118,6 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR

View File

@@ -118,5 +118,6 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i

View File

@@ -127,5 +127,6 @@ spring:

View File

@@ -1 +1 @@
spring.application.name=Langchain4j-heima20250803
spring.application.name=Langchain4j-cst20250803

View File

@@ -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>

View File

@@ -303,5 +303,6 @@

View File

@@ -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