|
|
@@ -0,0 +1,275 @@
|
|
|
+package shop.alien.store.util.ai;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.core.io.ByteArrayResource;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.LinkedMultiValueMap;
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+import shop.alien.entity.store.LifeFeedback;
|
|
|
+import shop.alien.store.feign.AlienAIFeign;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Component
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class AiFeedbackAssignUtils {
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Value("${ai.service.login-url}")
|
|
|
+ private String loginUrl;
|
|
|
+
|
|
|
+ @Value("${ai.service.username:UdUser}")
|
|
|
+ private String userName;
|
|
|
+
|
|
|
+ @Value("${ai.service.password:123456}")
|
|
|
+ private String passWord;
|
|
|
+
|
|
|
+ @Value("${ai.service.assign-staff-url}")
|
|
|
+ private String assignStaffUrl;
|
|
|
+
|
|
|
+ private final AlienAIFeign alienAIFeign;
|
|
|
+
|
|
|
+ // 语音识别接口地址(从配置中读取,如果没有配置则使用默认值)
|
|
|
+ @Value("${feign.alienAI.url}")
|
|
|
+ private String aiServiceBaseUrl;
|
|
|
+
|
|
|
+ /**登录的语音识别文件登录
|
|
|
+ * 登录 AI 服务,获取 token
|
|
|
+ *
|
|
|
+ * @return accessToken
|
|
|
+ */
|
|
|
+ public String getSpeechRecognition1(MultipartFile file,String accessToken,String language,String use_itn,String merge_vad) {
|
|
|
+ log.info("调用AI语音识别接口...");
|
|
|
+ try {
|
|
|
+ // 使用 RestTemplate 直接发送 multipart/form-data 请求
|
|
|
+ MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
|
|
|
+
|
|
|
+ // 添加音频文件
|
|
|
+ if (file != null && !file.isEmpty()) {
|
|
|
+ try {
|
|
|
+ formData.add("file", new ByteArrayResource(file.getBytes()) {
|
|
|
+ @Override
|
|
|
+ public String getFilename() {
|
|
|
+ String originalFilename = file.getOriginalFilename();
|
|
|
+ return originalFilename != null ? originalFilename : "audio_file";
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("读取音频文件失败", e);
|
|
|
+ throw new RuntimeException("读取音频文件失败", e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("音频文件为空,无法进行语音识别");
|
|
|
+ throw new RuntimeException("音频文件不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加识别语言参数(默认 auto)
|
|
|
+ if (StringUtils.hasText(language)) {
|
|
|
+ formData.add("language", language);
|
|
|
+ } else {
|
|
|
+ formData.add("language", "auto");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加是否开启逆文本标准化参数(默认 true)
|
|
|
+ // 支持传入 "true"/"false" 字符串或 boolean 值
|
|
|
+ if (StringUtils.hasText(use_itn)) {
|
|
|
+ // 确保传递的是有效的 boolean 字符串值
|
|
|
+ String useItnValue = use_itn.trim().toLowerCase();
|
|
|
+ if ("true".equals(useItnValue) || "false".equals(useItnValue)) {
|
|
|
+ formData.add("use_itn", useItnValue);
|
|
|
+ } else {
|
|
|
+ formData.add("use_itn", "true"); // 默认值
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ formData.add("use_itn", "true"); // 默认值
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加是否合并VAD参数(默认 true)
|
|
|
+ // 支持传入 "true"/"false" 字符串或 boolean 值
|
|
|
+ if (StringUtils.hasText(merge_vad)) {
|
|
|
+ // 确保传递的是有效的 boolean 字符串值
|
|
|
+ String mergeVadValue = merge_vad.trim().toLowerCase();
|
|
|
+ if ("true".equals(mergeVadValue) || "false".equals(mergeVadValue)) {
|
|
|
+ formData.add("merge_vad", mergeVadValue);
|
|
|
+ } else {
|
|
|
+ formData.add("merge_vad", "true"); // 默认值
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ formData.add("merge_vad", "true"); // 默认值
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
|
+ // 添加 Authorization header(如果需要)
|
|
|
+ if (StringUtils.hasText(accessToken)) {
|
|
|
+ headers.set("Authorization", "Bearer " + accessToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
|
|
+
|
|
|
+ // 构建完整的URL
|
|
|
+ String url = aiServiceBaseUrl + "/asr/upload";
|
|
|
+ log.info("调用AI语音识别接口,URL: {}", url);
|
|
|
+
|
|
|
+ // 使用 RestTemplate 发送请求
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
|
|
|
+
|
|
|
+ // 处理响应
|
|
|
+ if (response != null && response.getStatusCode() == HttpStatus.OK) {
|
|
|
+ String responseBody = response.getBody();
|
|
|
+ log.info("AI语音识别接口响应:{}", responseBody);
|
|
|
+ return responseBody;
|
|
|
+ } else {
|
|
|
+ log.error("AI语音识别接口调用失败,HTTP状态码: {}",
|
|
|
+ response != null ? response.getStatusCode() : "null");
|
|
|
+ throw new RuntimeException("AI语音识别接口调用失败,HTTP状态码: " +
|
|
|
+ (response != null ? response.getStatusCode() : "null"));
|
|
|
+ }
|
|
|
+ } catch (org.springframework.web.client.HttpClientErrorException e) {
|
|
|
+ log.error("调用AI语音识别接口失败,HTTP错误: {}, 响应体: {}",
|
|
|
+ e.getStatusCode(), e.getResponseBodyAsString(), e);
|
|
|
+ throw new RuntimeException("调用AI语音识别接口失败: " + e.getStatusCode() +
|
|
|
+ ", 响应: " + e.getResponseBodyAsString(), e);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI语音识别接口失败", e);
|
|
|
+ throw new RuntimeException("调用AI语音识别接口失败: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getAccessToken() {
|
|
|
+ log.info("登录AI服务获取token...{}", loginUrl);
|
|
|
+ MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
|
|
+ formData.add("username","admin");
|
|
|
+ formData.add("password", "123456");
|
|
|
+
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
+ HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
|
|
+
|
|
|
+ ResponseEntity<String> response;
|
|
|
+ try {
|
|
|
+ log.info("请求AI服务登录接口===================>");
|
|
|
+ response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("请求AI服务登录接口失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response != null && response.getStatusCode() == HttpStatus.OK) {
|
|
|
+ String body = response.getBody();
|
|
|
+ log.info("请求AI服务登录成功 postForEntity.getBody()\t{}", body);
|
|
|
+ if (StringUtils.hasText(body)) {
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(body);
|
|
|
+ if (jsonObject != null) {
|
|
|
+ JSONObject dataJson = jsonObject.getJSONObject("data");
|
|
|
+ if (dataJson != null) {
|
|
|
+ return dataJson.getString("access_token");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.warn("AI服务登录响应解析失败 body: {}", body);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.error("请求AI服务登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用AI接口分配跟踪人员
|
|
|
+ *
|
|
|
+ * @param feedback 反馈记录
|
|
|
+ * @param imageUrls 图片URL列表
|
|
|
+ * @param videoUrls 视频URL列表
|
|
|
+ * @return 分配的跟踪人员ID,失败返回null
|
|
|
+ */
|
|
|
+ public Integer assignStaffByAI(LifeFeedback feedback, List<String> imageUrls, List<String> videoUrls) {
|
|
|
+ try {
|
|
|
+ // 1. 获取访问令牌
|
|
|
+ String accessToken = getAccessToken();
|
|
|
+ if (accessToken == null) {
|
|
|
+ log.error("获取AI访问令牌失败,无法分配跟踪人员");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建请求参数
|
|
|
+ Map<String, Object> requestBody = new HashMap<>();
|
|
|
+ requestBody.put("feedback_id", feedback.getId());
|
|
|
+ requestBody.put("feedback_content", feedback.getContent());
|
|
|
+ requestBody.put("feedback_type", feedback.getFeedbackType());
|
|
|
+ requestBody.put("feedback_source", feedback.getFeedbackSource());
|
|
|
+ requestBody.put("feedback_way", feedback.getFeedbackWay());
|
|
|
+ requestBody.put("contact_way", feedback.getContactWay());
|
|
|
+ requestBody.put("user_id", feedback.getUserId());
|
|
|
+ if (imageUrls != null && !imageUrls.isEmpty()) {
|
|
|
+ requestBody.put("image_urls", imageUrls);
|
|
|
+ }
|
|
|
+ if (videoUrls != null && !videoUrls.isEmpty()) {
|
|
|
+ requestBody.put("video_urls", videoUrls);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 设置请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ headers.set("Authorization", "Bearer " + accessToken);
|
|
|
+
|
|
|
+ HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
|
|
|
+
|
|
|
+ // 4. 调用AI接口
|
|
|
+ log.info("调用AI分配跟踪人员接口,feedbackId={}, url={}", feedback.getId(), assignStaffUrl);
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(assignStaffUrl, requestEntity, String.class);
|
|
|
+
|
|
|
+ // 5. 处理响应
|
|
|
+ if (response != null && response.getStatusCodeValue() == 200) {
|
|
|
+ String responseBody = response.getBody();
|
|
|
+ log.info("AI分配跟踪人员接口响应:{}", responseBody);
|
|
|
+
|
|
|
+ if (StringUtils.hasText(responseBody)) {
|
|
|
+ JSONObject responseJson = JSONObject.parseObject(responseBody);
|
|
|
+ Integer code = responseJson.getInteger("code");
|
|
|
+
|
|
|
+ if (code != null && code == 200) {
|
|
|
+ JSONObject data = responseJson.getJSONObject("data");
|
|
|
+ if (data != null) {
|
|
|
+ Integer staffId = data.getInteger("staff_id");
|
|
|
+ if (staffId != null) {
|
|
|
+ log.info("AI分配跟踪人员成功,feedbackId={}, staffId={}, staffName={}",
|
|
|
+ feedback.getId(), staffId, data.getString("staff_name"));
|
|
|
+ return staffId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ String message = responseJson.getString("message");
|
|
|
+ log.error("AI分配跟踪人员失败,feedbackId={}, code={}, message={}",
|
|
|
+ feedback.getId(), code, message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.error("AI分配跟踪人员接口调用失败,feedbackId={}, http状态={}",
|
|
|
+ feedback.getId(), response != null ? response.getStatusCode() : null);
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
|
|
|
+ log.error("AI分配跟踪人员接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
|
|
|
+ return null;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI分配跟踪人员接口异常,feedbackId={}", feedback.getId(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|