Эх сурвалжийг харах

对话前端多用户使用实现

Zhangbw 2 сар өмнө
parent
commit
6be8a7cd3c
16 өөрчлөгдсөн 388 нэмэгдсэн , 51 устгасан
  1. 0 7
      ruoyi-admin/src/main/resources/application.yml
  2. 5 0
      ruoyi-modules/yp-talk/pom.xml
  3. 32 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/config/DifyConfig.java
  4. 41 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/config/XunfeiConfig.java
  5. 18 1
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/admin/TalkAgentController.java
  6. 22 3
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/api/ChatController.java
  7. 36 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/api/TalkConfigController.java
  8. 1 1
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/domain/bo/TalkUserBo.java
  9. 1 1
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/domain/vo/TalkAgentVo.java
  10. 3 1
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/IChatService.java
  11. 9 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/IDifyService.java
  12. 9 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/ITalkAgentService.java
  13. 48 25
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/ChatServiceImpl.java
  14. 106 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/DifyServiceImpl.java
  15. 51 0
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/TalkAgentServiceImpl.java
  16. 6 12
      ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/TtsServiceImpl.java

+ 0 - 7
ruoyi-admin/src/main/resources/application.yml

@@ -262,13 +262,6 @@ warm-flow:
   # 默认Authorization,如果有多个token,用逗号分隔
   token-name: ${sa-token.token-name},clientid
 
---- # 讯飞TTS配置
-xfyun:
-  tts:
-    appid: 0b53c170
-    api-key: 3915b5e04c7e118fea615889a1c94794
-    api-secret: ZTZjZmU3YzczMjdmNmQwMTc0ZDU3OTEw
-
 spring:
   web:
     resources:

+ 5 - 0
ruoyi-modules/yp-talk/pom.xml

@@ -18,6 +18,11 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
         <!-- RuoYi Common SaToken -->
         <dependency>
             <groupId>org.dromara</groupId>

+ 32 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/config/DifyConfig.java

@@ -0,0 +1,32 @@
+package org.dromara.talk.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.service.ConfigService;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DifyConfig {
+
+    private final ConfigService configService;
+
+    public String getApiKey() {
+        try {
+            return configService.getConfigValue("dify.apiKey");
+        } catch (Exception e) {
+            log.warn("从配置表读取 dify.apiKey 失败", e);
+        }
+        return "";
+    }
+
+    public String getApiUrl() {
+        try {
+            return configService.getConfigValue("dify.apiUrl");
+        } catch (Exception e) {
+            log.warn("从配置表读取 dify.apiUrl 失败", e);
+        }
+        return "";
+    }
+}

+ 41 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/config/XunfeiConfig.java

@@ -0,0 +1,41 @@
+package org.dromara.talk.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.service.ConfigService;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class XunfeiConfig {
+
+    private final ConfigService configService;
+
+    public String getAppId() {
+        try {
+            return configService.getConfigValue("xunfei.tts.appId");
+        } catch (Exception e) {
+            log.warn("从配置表读取 xunfei.tts.appId 失败", e);
+        }
+        return "";
+    }
+
+    public String getApiKey() {
+        try {
+            return configService.getConfigValue("xunfei.tts.apiKey");
+        } catch (Exception e) {
+            log.warn("从配置表读取 xunfei.tts.apiKey 失败", e);
+        }
+        return "";
+    }
+
+    public String getApiSecret() {
+        try {
+            return configService.getConfigValue("xunfei.tts.apiSecret");
+        } catch (Exception e) {
+            log.warn("从配置表读取 xunfei.tts.apiSecret 失败", e);
+        }
+        return "";
+    }
+}

+ 18 - 1
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/admin/TalkAgentController.java

@@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.dev33.satoken.annotation.SaIgnore;
 
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -136,4 +135,22 @@ public class TalkAgentController extends BaseController {
         return R.ok(dictService.getDictData("tts_vcn"));
     }
 
+    /**
+     * 改变客服状态
+     *
+     * @param id        客服ID
+     * @param newStatus 新状态
+     */
+    @Log(title = "客服配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/{id}/status/{newStatus}")
+    public R<Void> changeStatus(@NotNull(message = "客服ID不能为空") @PathVariable Long id,
+                                @NotNull(message = "状态不能为空") @PathVariable String newStatus) {
+        boolean success = talkAgentService.changeAgentStatus(id, newStatus);
+        if (success) {
+            return R.ok();
+        } else {
+            return R.fail("状态更新失败,客服可能已被其他用户占用");
+        }
+    }
+
 }

+ 22 - 3
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/api/ChatController.java

@@ -1,6 +1,5 @@
 package org.dromara.talk.controller.api;
 
-import cn.dev33.satoken.annotation.SaIgnore;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.dto.DictDataDTO;
@@ -54,10 +53,13 @@ public class ChatController {
         String agentGender = (String) request.get("agentGender");
         @SuppressWarnings("unchecked")
         List<Map<String, String>> ttsVcnList = (List<Map<String, String>>) request.get("ttsVcnList");
+        String conversationId = (String) request.get("conversationId");
+        Boolean isGreeting = request.get("isGreeting") != null ?
+            Boolean.valueOf(request.get("isGreeting").toString()) : false;
 
-        log.info("收到用户消息: {}, 客服ID: {}, 客服性别: {}", userMessage, agentId, agentGender);
+        log.info("收到用户消息: {}, 客服ID: {}, 客服性别: {}, 对话ID: {}, 是否欢迎语: {}", userMessage, agentId, agentGender, conversationId, isGreeting);
 
-        return chatService.processMessage(userMessage, agentId, agentGender, ttsVcnList);
+        return chatService.processMessage(userMessage, agentId, agentGender, ttsVcnList, conversationId, isGreeting);
     }
 
     /**
@@ -103,4 +105,21 @@ public class ChatController {
         boolean success = talkAgentService.updateByBo(bo);
         return Map.of("success", success);
     }
+
+    /**
+     * 挂断电话
+     * 用户挂断电话后,将客服状态改回0(正常)
+     *
+     * @param id 客服ID
+     * @return 更新结果
+     */
+    @PostMapping("/agent/{id}/hangup")
+    public Map<String, Object> hangupCall(@PathVariable Long id) {
+        TalkAgentBo bo = new TalkAgentBo();
+        bo.setId(id);
+        bo.setStatus("0");
+        boolean success = talkAgentService.updateByBo(bo);
+        log.info("客服 {} 挂断电话,状态已改为正常", id);
+        return Map.of("success", success);
+    }
 }

+ 36 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/api/TalkConfigController.java

@@ -0,0 +1,36 @@
+package org.dromara.talk.controller.api;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.talk.config.DifyConfig;
+import org.dromara.talk.config.XunfeiConfig;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 配置接口(对话前端)
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/talk/config")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class TalkConfigController {
+
+    private final XunfeiConfig xunfeiConfig;
+
+    /**
+     * 获取讯飞配置
+     */
+    @GetMapping("/xunfei")
+    public Map<String, String> getXunfeiConfig() {
+        Map<String, String> config = new HashMap<>();
+        config.put("appId", xunfeiConfig.getAppId());
+        config.put("apiKey", xunfeiConfig.getApiKey());
+        config.put("apiSecret", xunfeiConfig.getApiSecret());
+        return config;
+    }
+
+}

+ 1 - 1
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/domain/bo/TalkUserBo.java

@@ -29,7 +29,7 @@ public class TalkUserBo extends BaseEntity {
     /**
      * 用户名
      */
-    @NotBlank(message = "用户名不能为空", groups = { AddGroup.class, EditGroup.class })
+    @NotBlank(message = "用户名不能为空", groups = { AddGroup.class })
     private String username;
 
     /**

+ 1 - 1
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/domain/vo/TalkAgentVo.java

@@ -68,7 +68,7 @@ public class TalkAgentVo implements Serializable {
      * TTS发音人(字典)
      */
     @ExcelProperty(value = "TTS发音人", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(readConverterExp = "字=典")
+    @ExcelDictFormat(readConverterExp = "字典")
     private String ttsVcn;
 
     /**

+ 3 - 1
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/IChatService.java

@@ -15,7 +15,9 @@ public interface IChatService {
      * @param agentId 客服ID
      * @param agentGender 客服性别
      * @param ttsVcnList 发言人字典列表
+     * @param conversationId 对话ID(用于上下文回顾)
+     * @param isGreeting 是否为欢迎语(true时跳过dify工作流)
      * @return 响应数据(包含回复文本和音频)
      */
-    Map<String, Object> processMessage(String userMessage, Long agentId, String agentGender, List<Map<String, String>> ttsVcnList);
+    Map<String, Object> processMessage(String userMessage, Long agentId, String agentGender, List<Map<String, String>> ttsVcnList, String conversationId, Boolean isGreeting);
 }

+ 9 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/IDifyService.java

@@ -0,0 +1,9 @@
+package org.dromara.talk.service;
+
+import java.util.Map;
+
+public interface IDifyService {
+
+   Map<String, String> callWorkflow(String userMessage, Map<String, Object> inputs, Long userId, String conversationId);
+
+}

+ 9 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/ITalkAgentService.java

@@ -76,4 +76,13 @@ public interface ITalkAgentService {
      * @throws IOException IO异常
      */
     String uploadAvatar(MultipartFile file) throws IOException;
+
+    /**
+     * 改变客服状态(带并发控制)
+     *
+     * @param id        客服ID
+     * @param newStatus 新状态
+     * @return 是否修改成功
+     */
+    Boolean changeAgentStatus(Long id, String newStatus);
 }

+ 48 - 25
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/ChatServiceImpl.java

@@ -1,9 +1,11 @@
 package org.dromara.talk.service.impl;
 
+import cn.dev33.satoken.stp.StpUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.talk.domain.vo.TalkAgentVo;
 import org.dromara.talk.service.IChatService;
+import org.dromara.talk.service.IDifyService;
 import org.dromara.talk.service.ITalkAgentService;
 import org.dromara.talk.service.ITtsService;
 import org.springframework.stereotype.Service;
@@ -21,10 +23,11 @@ public class ChatServiceImpl implements IChatService {
 
     private final ITtsService ttsService;
     private final ITalkAgentService talkAgentService;
+    private final IDifyService difyService;
 
     @Override
-    public Map<String, Object> processMessage(String userMessage, Long agentId, String agentGender, List<Map<String, String>> ttsVcnList) {
-        log.info("处理用户消息: {}, 客服ID: {}, 客服性别: {}", userMessage, agentId, agentGender);
+    public Map<String, Object> processMessage(String userMessage, Long agentId, String agentGender, List<Map<String, String>> ttsVcnList, String conversationId, Boolean isGreeting) {
+        log.info("处理用户消息: {}, 客服ID: {}, 客服性别: {}, 对话ID: {}, 是否欢迎语: {}", userMessage, agentId, agentGender, conversationId, isGreeting);
 
         // 获取客服配置
         TalkAgentVo agentConfig = null;
@@ -32,14 +35,33 @@ public class ChatServiceImpl implements IChatService {
             agentConfig = talkAgentService.queryById(agentId);
         }
 
-        // 生成回复(预留AI接口,返回包含发言人和文本的JSON)
-        Map<String, String> aiResult = generateReply(userMessage, agentGender, ttsVcnList, agentConfig);
-        String reply = aiResult.get("replyText");
-        String selectedVcn = aiResult.get("ttsVcn");
+        String reply;
+        String newConversationId = conversationId;
+
+        // 如果是欢迎语,直接使用消息文本,不调用 Dify 工作流
+        if (Boolean.TRUE.equals(isGreeting)) {
+            log.info("处理欢迎语,跳过 Dify 工作流");
+            reply = userMessage;
+        } else {
+            // 获取当前登录用户ID
+            Long userId = null;
+            try {
+                userId = StpUtil.getLoginIdAsLong();
+            } catch (Exception e) {
+                log.warn("获取登录用户ID失败,使用默认值", e);
+                userId = 0L;
+            }
+
+            // 调用 Dify 生成回复
+            Map<String, String> aiResult = generateReply(userMessage, agentGender, ttsVcnList, agentConfig, userId, conversationId);
+            reply = aiResult.get("replyText");
+            String selectedVcn = aiResult.get("ttsVcn");
+            newConversationId = aiResult.get("conversationId");
 
-        // 如果AI选择了发言人,更新客服配置
-        if (selectedVcn != null && agentConfig != null) {
-            agentConfig.setTtsVcn(selectedVcn);
+            // 如果AI选择了发言人,更新客服配置
+            if (selectedVcn != null && agentConfig != null) {
+                agentConfig.setTtsVcn(selectedVcn);
+            }
         }
 
         // 合成语音(传递客服配置)
@@ -50,6 +72,9 @@ public class ChatServiceImpl implements IChatService {
         response.put("reply", reply);
         response.put("audio", audioBase64);
         response.put("timestamp", System.currentTimeMillis());
+        if (newConversationId != null) {
+            response.put("conversationId", newConversationId);
+        }
 
         log.info("消息处理完成: reply长度={}, audio长度={}",
             reply != null ? reply.length() : 0,
@@ -58,25 +83,23 @@ public class ChatServiceImpl implements IChatService {
         return response;
     }
 
-    private Map<String, String> generateReply(String userMessage, String agentGender, List<Map<String, String>> ttsVcnList, TalkAgentVo agentConfig) {
-        // 组装发送给AI的JSON数据
-        Map<String, Object> aiRequest = new HashMap<>();
-        aiRequest.put("userMessage", userMessage);
-        aiRequest.put("agentGender", agentGender);
-        aiRequest.put("ttsVcnList", ttsVcnList);
-        aiRequest.put("currentVcn", agentConfig != null ? agentConfig.getTtsVcn() : null);
-
-        log.info("AI接口预留 - 请求数据: {}", aiRequest);
+    private Map<String, String> generateReply(String userMessage, String agentGender,
+                                              List<Map<String, String>> ttsVcnList,
+                                              TalkAgentVo agentConfig,
+                                              Long userId,
+                                              String conversationId) {
+        // 组装发送给 Dify 的数据
+        Map<String, Object> inputs = new HashMap<>();
+        inputs.put("agentGender", agentGender);
+        inputs.put("ttsVcnList", ttsVcnList);
+        inputs.put("currentVcn", agentConfig != null ? agentConfig.getTtsVcn() : null);
 
-        // TODO: 后续接入AI,发送 aiRequest 给AI服务
-        // AI应返回格式: {"ttsVcn": "选择的发言人", "replyText": "回复文本"}
+        log.info("调用 Dify 工作流 - 用户ID: {}, 对话ID: {}", userId, conversationId);
 
-        // 暂时返回默认值
-        Map<String, String> aiResponse = new HashMap<>();
-        aiResponse.put("ttsVcn", agentConfig != null ? agentConfig.getTtsVcn() : "x4_yezi");
-        aiResponse.put("replyText", userMessage);
+        // 调用 Dify 工作流
+        Map<String, String> aiResponse = difyService.callWorkflow(userMessage, inputs, userId, conversationId);
 
-        log.info("AI接口预留 - 响应数据: {}", aiResponse);
+        log.info("Dify 工作流响应: {}", aiResponse);
 
         return aiResponse;
     }

+ 106 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/DifyServiceImpl.java

@@ -0,0 +1,106 @@
+package org.dromara.talk.service.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import cn.hutool.json.JSONObject;
+import okhttp3.*;
+import org.dromara.talk.config.DifyConfig;
+import org.dromara.talk.service.IDifyService;
+import org.springframework.stereotype.Service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DifyServiceImpl implements IDifyService {
+    private final DifyConfig difyConfig;
+    private final OkHttpClient httpClient = new OkHttpClient();
+
+    public Map<String, String> callWorkflow(String userMessage, Map<String, Object> inputs, Long userId, String conversationId) {
+        try {
+            log.info("调用 Dify 工作流 - userId: {}, conversationId: {}, message: {}", userId, conversationId, userMessage);
+
+            // 构建请求体
+            JSONObject requestBody = new JSONObject();
+            requestBody.set("inputs", inputs);
+            requestBody.set("query", userMessage);
+            requestBody.set("response_mode", "blocking");
+            requestBody.set("user", "user-" + userId);
+
+            // 如果有对话ID,添加到请求中以支持上下文回顾
+            if (conversationId != null && !conversationId.isEmpty()) {
+                requestBody.set("conversation_id", conversationId);
+                log.info("添加 conversation_id 到请求: {}", conversationId);
+            } else {
+                log.info("conversationId 为空,这是首次对话");
+            }
+
+            log.info("Dify API 请求体: {}", requestBody.toString());
+
+            // 发送请求
+            Request request = new Request.Builder()
+                .url(difyConfig.getApiUrl() + "/chat-messages")
+                .post(RequestBody.create(
+                    requestBody.toString(),
+                    MediaType.parse("application/json")))
+                .addHeader("Authorization", "Bearer " + difyConfig.getApiKey())
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+            Response response = httpClient.newCall(request).execute();
+            String responseBody = response.body().string();
+
+            log.info("Dify API 响应: {}", responseBody);
+
+            // 解析响应
+            JSONObject result = new JSONObject(responseBody);
+            Map<String, String> aiResponse = new HashMap<>();
+
+            // 检查是否有错误响应
+            if (result.containsKey("code") && result.containsKey("message")) {
+                String errorCode = result.getStr("code");
+                String errorMessage = result.getStr("message");
+                log.error("Dify API 返回错误: code={}, message={}", errorCode, errorMessage);
+                throw new RuntimeException("Dify API 错误: " + errorMessage);
+            }
+
+            // Dify API 直接返回 answer 字段,不是嵌套在 data 中
+            String replyText = result.getStr("answer");
+            if (replyText == null) {
+                log.error("Dify API 响应中没有 answer 字段,完整响应: {}", responseBody);
+                throw new RuntimeException("Dify API 响应格式错误");
+            }
+
+            aiResponse.put("replyText", replyText);
+
+            // 从 outputs 中获取 ttsVcn(如果有的话)
+            String ttsVcn = null;
+            if (result.containsKey("outputs")) {
+                JSONObject outputs = result.getJSONObject("outputs");
+                if (outputs != null) {
+                    ttsVcn = outputs.getStr("ttsVcn");
+                }
+            }
+
+            // 如果AI没有返回发言人,使用客服配置中的默认发言人
+            if (ttsVcn == null) {
+                ttsVcn = inputs.get("currentVcn") != null ? inputs.get("currentVcn").toString() : "x4_yezi";
+            }
+            aiResponse.put("ttsVcn", ttsVcn);
+
+            // 返回对话ID以便前端保存,用于下次请求
+            String newConversationId = result.getStr("conversation_id");
+            if (newConversationId != null) {
+                aiResponse.put("conversationId", newConversationId);
+            }
+
+            return aiResponse;
+        } catch (Exception e) {
+            log.error("调用 Dify 工作流失败", e);
+            throw new RuntimeException("AI 服务调用失败", e);
+        }
+    }
+}

+ 51 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/TalkAgentServiceImpl.java

@@ -174,4 +174,55 @@ public class TalkAgentServiceImpl implements ITalkAgentService {
 
         return "/uploads/avatars/" + fileName;
     }
+
+    /**
+     * 改变客服状态(带并发控制)
+     * 使用乐观锁机制确保状态更新的原子性
+     *
+     * @param id        客服ID
+     * @param newStatus 新状态
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean changeAgentStatus(Long id, String newStatus) {
+        // 查询当前客服信息
+        TalkAgent agent = baseMapper.selectById(id);
+        if (agent == null) {
+            log.warn("客服不存在: id={}", id);
+            return false;
+        }
+
+        // 如果要设置为对话中状态,需要检查当前状态
+        if ("2".equals(newStatus)) {
+            // 只有状态为0(正常)的客服才能被设置为对话中
+            if (!"0".equals(agent.getStatus())) {
+                log.warn("客服状态不允许转换为对话中: id={}, currentStatus={}", id, agent.getStatus());
+                return false;
+            }
+
+            // 使用条件更新确保原子性:只有当状态仍为0时才更新为2
+            int updated = baseMapper.update(null,
+                Wrappers.<TalkAgent>lambdaUpdate()
+                    .eq(TalkAgent::getId, id)
+                    .eq(TalkAgent::getStatus, "0")  // 条件:当前状态必须为0
+                    .set(TalkAgent::getStatus, "2")  // 更新为2
+            );
+
+            if (updated == 0) {
+                log.warn("客服状态更新失败,可能已被其他用户占用: id={}", id);
+                return false;
+            }
+
+            log.info("客服状态已更新为对话中: id={}", id);
+            return true;
+        }
+
+        // 其他状态转换直接更新
+        agent.setStatus(newStatus);
+        boolean result = baseMapper.updateById(agent) > 0;
+        if (result) {
+            log.info("客服状态已更新: id={}, newStatus={}", id, newStatus);
+        }
+        return result;
+    }
 }

+ 6 - 12
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/TtsServiceImpl.java

@@ -1,13 +1,13 @@
 package org.dromara.talk.service.impl;
 
 import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONObject;
+import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.talk.config.XunfeiConfig;
 import org.dromara.talk.domain.vo.TalkAgentVo;
 import org.dromara.talk.service.ITtsService;
 import org.java_websocket.client.WebSocketClient;
 import org.java_websocket.handshake.ServerHandshake;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import javax.crypto.Mac;
@@ -21,23 +21,17 @@ import java.util.*;
 
 @Slf4j
 @Service
+@AllArgsConstructor
 public class TtsServiceImpl implements ITtsService {
 
-    @Value("${xfyun.tts.appid:}")
-    private String appId;
-
-    @Value("${xfyun.tts.api-key:}")
-    private String apiKey;
-
-    @Value("${xfyun.tts.api-secret:}")
-    private String apiSecret;
+    private final XunfeiConfig xunfeiConfig;
 
     private static final String HOST_URL = "https://tts-api.xfyun.cn/v2/tts";
 
     @Override
     public void synthesize(String text, TalkAgentVo agentConfig, ITtsService.AudioCallback callback) {
         try {
-            String wsUrl = getAuthUrl(HOST_URL, apiKey, apiSecret).replace("https://", "wss://");
+            String wsUrl = getAuthUrl(HOST_URL, xunfeiConfig.getApiKey(), xunfeiConfig.getApiSecret()).replace("https://", "wss://");
             URI uri = new URI(wsUrl);
             String requestJson = buildRequest(text, agentConfig);
 
@@ -93,7 +87,7 @@ public class TtsServiceImpl implements ITtsService {
         Long bgs = agentConfig != null && agentConfig.getTtsBgs() != null ? agentConfig.getTtsBgs() : 0L;
 
         return "{\n" +
-                "  \"common\": {\"app_id\": \"" + appId + "\"},\n" +
+                "  \"common\": {\"app_id\": \"" + xunfeiConfig.getAppId() + "\"},\n" +
                 "  \"business\": {\n" +
                 "    \"aue\": \"lame\",\n" +
                 "    \"tte\": \"UTF8\",\n" +