Explorar el Código

结构规范化
whatsapp接收信息接口新增

Zhangbw hace 2 meses
padre
commit
ff1653c0f0

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

@@ -118,6 +118,7 @@ security:
     - /warm-flow-ui/config
     - /talk/auth/**
     - /talk/message/stream
+    - /talk/whatsapp/**
 
 # 多租户配置
 tenant:

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

@@ -1,29 +1,20 @@
 package org.dromara.talk.controller.api;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.dto.DictDataDTO;
 import org.dromara.common.core.service.DictService;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.talk.domain.bo.TalkAgentBo;
-import org.dromara.talk.domain.bo.TalkSessionBo;
 import org.dromara.talk.domain.dto.MessageStreamRequest;
 import org.dromara.talk.domain.vo.TalkAgentVo;
 import org.dromara.talk.service.IChatService;
 import org.dromara.talk.service.ITalkAgentService;
-import org.dromara.talk.service.ITalkSessionService;
-import org.dromara.talk.service.IPhoneUserService;
-import org.dromara.talk.domain.bo.PhoneUserBo;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.UUID;
 
 /**
  * 对话接口(对话前端)
@@ -41,8 +32,6 @@ public class ChatController {
     private final IChatService chatService;
     private final DictService dictService;
     private final ITalkAgentService talkAgentService;
-    private final ITalkSessionService talkSessionService;
-    private final IPhoneUserService phoneUserService;
 
     /**
      * 获取TTS发言人字典
@@ -107,47 +96,15 @@ public class ChatController {
      */
     @PostMapping("/agent/{id}/start")
     public Map<String, Object> startChat(@PathVariable Long id, @RequestBody(required = false) Map<String, String> request) {
-        boolean success = talkAgentService.changeAgentStatus(id, "2");
-        if (success) {
-            Long userId = 0L;
-            try {
-                userId = LoginHelper.getUserId();
-            } catch (Exception e) {
-                log.warn("获取登录用户ID失败,使用默认值", e);
-            }
-
-            String tempSessionId = UUID.randomUUID().toString();
-            String customerPhone = request != null ? request.get("customerPhone") : null;
-
-            // 如果有客户电话,检查并保存到 phone_user 表
-            if (customerPhone != null && !customerPhone.isEmpty()) {
-                try {
-                    PhoneUserBo phoneUserBo = new PhoneUserBo();
-                    phoneUserBo.setCustomerPhone(customerPhone);
-                    phoneUserService.insertByBo(phoneUserBo);
-                    log.info("新客户电话已保存: {}", customerPhone);
-                } catch (Exception e) {
-                    // 如果插入失败(可能是重复),忽略错误继续执行
-                    log.debug("客户电话可能已存在: {}", customerPhone);
-                }
-            }
-
-            TalkSessionBo sessionBo = new TalkSessionBo();
-            sessionBo.setSessionId(tempSessionId);
-            sessionBo.setAgentId(id);
-            sessionBo.setStartTime(new Date());
-            sessionBo.setUserId(userId);
-            if (customerPhone != null) {
-                sessionBo.setCustomerPhone(customerPhone);
-            }
-            talkSessionService.insertByBo(sessionBo);
-
-            log.info("客服 {} 开始对话,sessionId: {}, customerPhone: {}, 创建人: {}", id, tempSessionId, customerPhone, userId);
-            return Map.of("success", true, "code", 200, "sessionId", tempSessionId);
-        } else {
-            log.warn("客服 {} 开始对话失败,可能已被占用", id);
-            return Map.of("success", false, "code", 400, "msg", "该客服已被其他用户占用,请选择其他客服");
+        Long userId = 0L;
+        try {
+            userId = LoginHelper.getUserId();
+        } catch (Exception e) {
+            log.warn("获取登录用户ID失败,使用默认值", e);
         }
+
+        String customerPhone = request != null ? request.get("customerPhone") : null;
+        return chatService.startChatSession(id, userId, customerPhone);
     }
 
     /**
@@ -163,42 +120,7 @@ public class ChatController {
         String sessionId = (String) request.get("sessionId");
         List<Map<String, Object>> chatHistory = (List<Map<String, Object>>) request.get("chatHistory");
 
-        // 更新会话结束时间并保存对话内容
-        if (sessionId != null) {
-            talkSessionService.updateEndTime(sessionId);
-
-            // 保存对话内容
-            if (chatHistory != null && !chatHistory.isEmpty()) {
-                try {
-                    // 转换前端格式到后端格式
-                    List<Map<String, String>> messages = new ArrayList<>();
-                    for (Map<String, Object> msg : chatHistory) {
-                        Map<String, String> message = new HashMap<>();
-                        String type = (String) msg.get("type");
-                        String content = (String) msg.get("content");
-
-                        // 转换 type: user/agent 到 role: user/assistant
-                        message.put("role", "user".equals(type) ? "user" : "assistant");
-                        message.put("content", content);
-                        message.put("timestamp", String.valueOf(System.currentTimeMillis()));
-                        messages.add(message);
-                    }
-
-                    String conversationJson = new ObjectMapper().writeValueAsString(messages);
-                    talkSessionService.saveOrUpdateConversation(sessionId, id, conversationJson, null, null);
-                    log.info("挂断时保存对话内容,会话ID: {}, 消息数量: {}", sessionId, messages.size());
-                } catch (Exception e) {
-                    log.error("保存对话内容失败", e);
-                }
-            }
-        }
-
-        // 将客服状态改为空闲
-        TalkAgentBo bo = new TalkAgentBo();
-        bo.setId(id);
-        bo.setStatus("0");
-        boolean success = talkAgentService.updateByBo(bo);
-        log.info("客服 {} 挂断电话,状态已改为空闲中,会话ID: {}", id, sessionId);
+        boolean success = chatService.hangupCall(id, sessionId, chatHistory);
         return Map.of("success", success);
     }
 

+ 5 - 68
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/controller/api/TalkAuthController.java

@@ -2,25 +2,14 @@ package org.dromara.talk.controller.api;
 
 import cn.dev33.satoken.annotation.SaIgnore;
 import cn.dev33.satoken.stp.StpUtil;
-import cn.dev33.satoken.stp.parameter.SaLoginParameter;
-import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.domain.R;
-import org.dromara.common.core.domain.model.LoginUser;
-import org.dromara.common.core.enums.UserType;
-import org.dromara.common.core.utils.MessageUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
-import org.dromara.system.domain.vo.SysClientVo;
-import org.dromara.system.service.ISysClientService;
-import org.dromara.talk.domain.TalkUser;
 import org.dromara.talk.domain.vo.TalkUserVo;
 import org.dromara.talk.service.ITalkUserService;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -38,7 +27,6 @@ import java.util.Map;
 public class TalkAuthController {
 
     private final ITalkUserService talkUserService;
-    private final ISysClientService clientService;
 
     /**
      * 登录
@@ -51,63 +39,12 @@ public class TalkAuthController {
         String username = request.get("username");
         String password = request.get("password");
 
-        // 验证 clientId 是否存在
-        if (ObjectUtil.isNull(clientId)) {
-            log.info("客户端id为空");
-            return R.fail("客户端ID不能为空");
+        try {
+            Map<String, Object> result = talkUserService.login(username, password, clientId);
+            return R.ok(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
         }
-
-        // 查询客户端配置
-        SysClientVo client = clientService.queryByClientId(clientId);
-        if (ObjectUtil.isNull(client)) {
-            log.info("客户端id: {} 不存在", clientId);
-            return R.fail(MessageUtils.message("auth.grant.type.error"));
-        } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
-            return R.fail(MessageUtils.message("auth.grant.type.blocked"));
-        }
-
-        // 查询用户(包含密码)
-        TalkUser user = talkUserService.queryByUsernameWithPassword(username);
-        if (user == null) {
-            return R.fail("用户名或密码错误");
-        }
-
-        // 验证密码
-        if (!password.equals(user.getPassword())) {
-            return R.fail("用户名或密码错误");
-        }
-
-        // 检查用户状态
-        if ("1".equals(user.getStatus())) {
-            return R.fail("用户已被停用");
-        }
-
-        // 创建 LoginUser 对象(不包含任何权限)
-        LoginUser loginUser = new LoginUser();
-        loginUser.setUserId(user.getId());
-        loginUser.setUsername(user.getUsername());
-        loginUser.setUserType(UserType.APP_USER.getUserType());
-        loginUser.setMenuPermission(Collections.emptySet());  // 空权限列表
-        loginUser.setRolePermission(Collections.emptySet());  // 空角色列表
-        loginUser.setClientKey(client.getClientKey());
-        loginUser.setDeviceType(client.getDeviceType());
-
-        // 使用 LoginHelper 登录,使用 sys_client 表中的配置
-        SaLoginParameter model = new SaLoginParameter();
-        model.setDeviceType(client.getDeviceType());
-        model.setTimeout(client.getTimeout());
-        model.setActiveTimeout(client.getActiveTimeout());
-        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
-        LoginHelper.login(loginUser, model);
-
-        // 返回token和用户信息(不包含密码)
-        TalkUserVo userVo = talkUserService.queryByUsername(username);
-        Map<String, Object> result = new HashMap<>();
-        result.put("token", StpUtil.getTokenValue());
-        result.put("user", userVo);
-        result.put("clientId", client.getClientId());
-
-        return R.ok(result);
     }
 
     /**

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

@@ -0,0 +1,35 @@
+package org.dromara.talk.controller.api;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.talk.domain.dto.WhatsAppMessageRequest;
+import org.dromara.talk.service.IWhatsAppHistoryService;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * WhatsApp历史记录API接口(对外)
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/talk/whatsapp/history")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class WhatsAppHistoryApiController {
+
+    private final IWhatsAppHistoryService whatsAppHistoryService;
+
+    /**
+     * 接收WhatsApp消息
+     */
+    @PostMapping("/receive")
+    public R<Void> receiveMessage(@RequestBody WhatsAppMessageRequest request) {
+        try {
+            whatsAppHistoryService.receiveMessage(request);
+            return R.ok();
+        } catch (Exception e) {
+            log.error("保存WhatsApp消息失败", e);
+            return R.fail("保存消息失败: " + e.getMessage());
+        }
+    }
+}

+ 44 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/domain/dto/WhatsAppMessageRequest.java

@@ -0,0 +1,44 @@
+package org.dromara.talk.domain.dto;
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * WhatsApp消息接收请求
+ */
+@Data
+public class WhatsAppMessageRequest {
+
+    /**
+     * 会话ID
+     */
+    private String sessionId;
+
+    /**
+     * 用户账号
+     */
+    private String userAccount;
+
+    /**
+     * 对话历史
+     */
+    private List<ConversationMessage> conversationHistory;
+
+    @Data
+    public static class ConversationMessage {
+        /**
+         * 角色: user/assistant
+         */
+        private String role;
+
+        /**
+         * 消息内容
+         */
+        private String content;
+
+        /**
+         * 时间戳
+         */
+        private String timestamp;
+    }
+}

+ 20 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/IChatService.java

@@ -25,4 +25,24 @@ public interface IChatService {
      * @param emitter SSE发射器
      */
     void processMessageStream(String userMessage, Long agentId, String agentGender, List<Map<String, String>> ttsVcnList, String conversationId, Boolean isGreeting, Integer requestId, String customerPhone, Integer type, SseEmitter emitter);
+
+    /**
+     * 开始对话会话
+     *
+     * @param agentId 客服ID
+     * @param userId 用户ID
+     * @param customerPhone 客户电话(可选)
+     * @return 包含成功状态和sessionId的Map
+     */
+    Map<String, Object> startChatSession(Long agentId, Long userId, String customerPhone);
+
+    /**
+     * 挂断电话
+     *
+     * @param agentId 客服ID
+     * @param sessionId 会话ID
+     * @param chatHistory 对话历史记录
+     * @return 是否成功
+     */
+    Boolean hangupCall(Long agentId, String sessionId, List<Map<String, Object>> chatHistory);
 }

+ 11 - 0
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/ITalkUserService.java

@@ -8,6 +8,7 @@ import org.dromara.talk.domain.vo.TalkUserVo;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 对话用户Service接口
@@ -77,4 +78,14 @@ public interface ITalkUserService {
      */
     TalkUserVo queryByUsername(String username);
 
+    /**
+     * 用户登录
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @param clientId 客户端ID
+     * @return 登录结果,包含token和用户信息
+     */
+    Map<String, Object> login(String username, String password, String clientId);
+
 }

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

@@ -2,6 +2,7 @@ package org.dromara.talk.service;
 
 import org.dromara.talk.domain.vo.WhatsAppHistoryVo;
 import org.dromara.talk.domain.bo.WhatsAppHistoryBo;
+import org.dromara.talk.domain.dto.WhatsAppMessageRequest;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 
@@ -65,4 +66,12 @@ public interface IWhatsAppHistoryService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 接收并保存WhatsApp消息
+     *
+     * @param request WhatsApp消息请求
+     * @return 是否保存成功
+     */
+    Boolean receiveMessage(WhatsAppMessageRequest request);
 }

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

@@ -4,16 +4,22 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.talk.domain.bo.PhoneUserBo;
+import org.dromara.talk.domain.bo.TalkAgentBo;
+import org.dromara.talk.domain.bo.TalkSessionBo;
 import org.dromara.talk.domain.vo.TalkAgentVo;
 import org.dromara.talk.domain.vo.TalkSessionVo;
 import org.dromara.talk.service.IChatService;
 import org.dromara.talk.service.IDifyService;
+import org.dromara.talk.service.IPhoneUserService;
 import org.dromara.talk.service.ITalkAgentService;
 import org.dromara.talk.service.ITalkSessionService;
 import org.dromara.talk.service.ITtsService;
 import org.springframework.stereotype.Service;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
+import java.util.Date;
+
 import java.io.ByteArrayOutputStream;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
@@ -30,6 +36,7 @@ public class ChatServiceImpl implements IChatService {
     private final ITalkAgentService talkAgentService;
     private final IDifyService difyService;
     private final ITalkSessionService talkSessionService;
+    private final IPhoneUserService phoneUserService;
 
     // 存储每个用户的最新请求ID
     private final Map<Long, Integer> latestRequestIdMap = new ConcurrentHashMap<>();
@@ -277,4 +284,81 @@ public class ChatServiceImpl implements IChatService {
             }
         });
     }
+
+    @Override
+    public Map<String, Object> startChatSession(Long agentId, Long userId, String customerPhone) {
+        boolean success = talkAgentService.changeAgentStatus(agentId, "2");
+        if (!success) {
+            log.warn("客服 {} 开始对话失败,可能已被占用", agentId);
+            return Map.of("success", false, "code", 400, "msg", "该客服已被其他用户占用,请选择其他客服");
+        }
+
+        String tempSessionId = UUID.randomUUID().toString();
+
+        // 如果有客户电话,检查并保存到 phone_user 表
+        if (customerPhone != null && !customerPhone.isEmpty()) {
+            try {
+                PhoneUserBo phoneUserBo = new PhoneUserBo();
+                phoneUserBo.setCustomerPhone(customerPhone);
+                phoneUserService.insertByBo(phoneUserBo);
+                log.info("新客户电话已保存: {}", customerPhone);
+            } catch (Exception e) {
+                log.debug("客户电话可能已存在: {}", customerPhone);
+            }
+        }
+
+        TalkSessionBo sessionBo = new TalkSessionBo();
+        sessionBo.setSessionId(tempSessionId);
+        sessionBo.setAgentId(agentId);
+        sessionBo.setStartTime(new Date());
+        sessionBo.setUserId(userId);
+        if (customerPhone != null) {
+            sessionBo.setCustomerPhone(customerPhone);
+        }
+        talkSessionService.insertByBo(sessionBo);
+
+        log.info("客服 {} 开始对话,sessionId: {}, customerPhone: {}, 创建人: {}", agentId, tempSessionId, customerPhone, userId);
+        return Map.of("success", true, "code", 200, "sessionId", tempSessionId);
+    }
+
+    @Override
+    public Boolean hangupCall(Long agentId, String sessionId, List<Map<String, Object>> chatHistory) {
+        // 更新会话结束时间并保存对话内容
+        if (sessionId != null) {
+            talkSessionService.updateEndTime(sessionId);
+
+            // 保存对话内容
+            if (chatHistory != null && !chatHistory.isEmpty()) {
+                try {
+                    // 转换前端格式到后端格式
+                    List<Map<String, String>> messages = new ArrayList<>();
+                    for (Map<String, Object> msg : chatHistory) {
+                        Map<String, String> message = new HashMap<>();
+                        String type = (String) msg.get("type");
+                        String content = (String) msg.get("content");
+
+                        // 转换 type: user/agent 到 role: user/assistant
+                        message.put("role", "user".equals(type) ? "user" : "assistant");
+                        message.put("content", content);
+                        message.put("timestamp", String.valueOf(System.currentTimeMillis()));
+                        messages.add(message);
+                    }
+
+                    String conversationJson = new ObjectMapper().writeValueAsString(messages);
+                    talkSessionService.saveOrUpdateConversation(sessionId, agentId, conversationJson, null, null);
+                    log.info("挂断时保存对话内容,会话ID: {}, 消息数量: {}", sessionId, messages.size());
+                } catch (Exception e) {
+                    log.error("保存对话内容失败", e);
+                }
+            }
+        }
+
+        // 将客服状态改为空闲
+        TalkAgentBo bo = new TalkAgentBo();
+        bo.setId(agentId);
+        bo.setStatus("0");
+        boolean success = talkAgentService.updateByBo(bo);
+        log.info("客服 {} 挂断电话,状态已改为空闲中,会话ID: {}", agentId, sessionId);
+        return success;
+    }
 }

+ 73 - 3
ruoyi-modules/yp-talk/src/main/java/org/dromara/talk/service/impl/TalkUserServiceImpl.java

@@ -1,14 +1,24 @@
 package org.dromara.talk.service.impl;
 
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.SystemConstants;
+import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.enums.UserType;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.MessageUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.domain.vo.SysClientVo;
+import org.dromara.system.service.ISysClientService;
 import org.dromara.talk.domain.TalkUser;
 import org.dromara.talk.domain.bo.TalkUserBo;
 import org.dromara.talk.domain.vo.TalkUserVo;
@@ -16,9 +26,7 @@ import org.dromara.talk.mapper.TalkUserMapper;
 import org.dromara.talk.service.ITalkUserService;
 import org.springframework.stereotype.Service;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 对话用户Service业务层处理
@@ -32,6 +40,7 @@ import java.util.Map;
 public class TalkUserServiceImpl implements ITalkUserService {
 
     private final TalkUserMapper baseMapper;
+    private final ISysClientService clientService;
 
     /**
      * 查询对话用户
@@ -148,4 +157,65 @@ public class TalkUserServiceImpl implements ITalkUserService {
             .eq(TalkUser::getUsername, username));
     }
 
+    @Override
+    public Map<String, Object> login(String username, String password, String clientId) {
+        // 验证 clientId 是否存在
+        if (ObjectUtil.isNull(clientId)) {
+            log.info("客户端id为空");
+            throw new IllegalArgumentException("客户端ID不能为空");
+        }
+
+        // 查询客户端配置
+        SysClientVo client = clientService.queryByClientId(clientId);
+        if (ObjectUtil.isNull(client)) {
+            log.info("客户端id: {} 不存在", clientId);
+            throw new IllegalArgumentException(MessageUtils.message("auth.grant.type.error"));
+        } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
+            throw new IllegalArgumentException(MessageUtils.message("auth.grant.type.blocked"));
+        }
+
+        // 查询用户(包含密码)
+        TalkUser user = queryByUsernameWithPassword(username);
+        if (user == null) {
+            throw new IllegalArgumentException("用户名或密码错误");
+        }
+
+        // 验证密码
+        if (!password.equals(user.getPassword())) {
+            throw new IllegalArgumentException("用户名或密码错误");
+        }
+
+        // 检查用户状态
+        if ("1".equals(user.getStatus())) {
+            throw new IllegalArgumentException("用户已被停用");
+        }
+
+        // 创建 LoginUser 对象(不包含任何权限)
+        LoginUser loginUser = new LoginUser();
+        loginUser.setUserId(user.getId());
+        loginUser.setUsername(user.getUsername());
+        loginUser.setUserType(UserType.APP_USER.getUserType());
+        loginUser.setMenuPermission(Collections.emptySet());
+        loginUser.setRolePermission(Collections.emptySet());
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+
+        // 使用 LoginHelper 登录
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        LoginHelper.login(loginUser, model);
+
+        // 返回token和用户信息
+        TalkUserVo userVo = queryByUsername(username);
+        Map<String, Object> result = new HashMap<>();
+        result.put("token", StpUtil.getTokenValue());
+        result.put("user", userVo);
+        result.put("clientId", client.getClientId());
+
+        return result;
+    }
+
 }

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

@@ -15,10 +15,15 @@ import org.dromara.talk.domain.vo.WhatsAppHistoryVo;
 import org.dromara.talk.domain.WhatsAppHistory;
 import org.dromara.talk.mapper.WhatsAppHistoryMapper;
 import org.dromara.talk.service.IWhatsAppHistoryService;
+import org.dromara.talk.service.IWhatsAppUserService;
+import org.dromara.talk.domain.bo.WhatsAppUserBo;
+import org.dromara.talk.domain.dto.WhatsAppMessageRequest;
+import com.alibaba.fastjson2.JSON;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
+import java.util.Date;
 
 /**
  * WhatsApp历史记录Service业务层处理
@@ -32,6 +37,7 @@ import java.util.Collection;
 public class WhatsAppHistoryServiceImpl implements IWhatsAppHistoryService {
 
     private final WhatsAppHistoryMapper baseMapper;
+    private final IWhatsAppUserService whatsAppUserService;
 
     /**
      * 查询WhatsApp历史记录
@@ -132,4 +138,36 @@ public class WhatsAppHistoryServiceImpl implements IWhatsAppHistoryService {
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 接收并保存WhatsApp消息
+     *
+     * @param request WhatsApp消息请求
+     * @return 是否保存成功
+     */
+    @Override
+    public Boolean receiveMessage(WhatsAppMessageRequest request) {
+        log.info("接收到WhatsApp消息 - sessionId: {}, userAccount: {}",
+            request.getSessionId(), request.getUserAccount());
+
+        // 1. 检查并保存用户
+        WhatsAppUserBo userBo = new WhatsAppUserBo();
+        userBo.setUserAccount(request.getUserAccount());
+        try {
+            whatsAppUserService.insertByBo(userBo);
+            log.info("新用户已保存: {}", request.getUserAccount());
+        } catch (Exception e) {
+            // 用户可能已存在,忽略错误
+            log.debug("用户可能已存在: {}", request.getUserAccount());
+        }
+
+        // 2. 保存对话历史
+        WhatsAppHistoryBo historyBo = new WhatsAppHistoryBo();
+        historyBo.setSessionId(request.getSessionId());
+        historyBo.setUserAccount(request.getUserAccount());
+        historyBo.setConversationJson(JSON.toJSONString(request.getConversationHistory()));
+        historyBo.setStartTime(new Date());
+
+        return insertByBo(historyBo);
+    }
 }