diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java index 515dcf2..fa1664a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundController.java @@ -3,6 +3,7 @@ package com.ruoyi.web.controller.jarvis; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.jarvis.domain.dto.WeComInboundRequest; import com.ruoyi.jarvis.service.IWeComInboundService; +import com.ruoyi.jarvis.service.IWeComInboundTraceService; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; @@ -25,6 +26,8 @@ public class WeComInboundController { @Resource private IWeComInboundService weComInboundService; + @Resource + private IWeComInboundTraceService weComInboundTraceService; @PostMapping("/inbound") public AjaxResult inbound( @@ -33,7 +36,9 @@ public class WeComInboundController { if (!StringUtils.hasText(inboundSecret) || !inboundSecret.equals(secret)) { return AjaxResult.error("拒绝访问"); } - String reply = weComInboundService.handleInbound(body != null ? body : new WeComInboundRequest()); + WeComInboundRequest req = body != null ? body : new WeComInboundRequest(); + String reply = weComInboundService.handleInbound(req); + weComInboundTraceService.recordInbound(req, reply); Map data = new HashMap<>(2); data.put("reply", reply != null ? reply : ""); return AjaxResult.success(data); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundTraceController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundTraceController.java new file mode 100644 index 0000000..864d470 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jarvis/WeComInboundTraceController.java @@ -0,0 +1,46 @@ +package com.ruoyi.web.controller.jarvis; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.jarvis.domain.WeComInboundTrace; +import com.ruoyi.jarvis.service.IWeComInboundTraceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 企微 inbound 消息追踪查询 + */ +@RestController +@RequestMapping("/jarvis/wecom/inboundTrace") +public class WeComInboundTraceController extends BaseController { + + @Autowired + private IWeComInboundTraceService weComInboundTraceService; + + @PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:list')") + @GetMapping("/list") + public TableDataInfo list(WeComInboundTrace query) { + startPage(); + List list = weComInboundTraceService.selectWeComInboundTraceList(query); + return getDataTable(list); + } + + @PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:list')") + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable Long id) { + return success(weComInboundTraceService.selectWeComInboundTraceById(id)); + } + + @PreAuthorize("@ss.hasPermi('jarvis:wecom:inboundTrace:remove')") + @Log(title = "企微消息跟踪", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(weComInboundTraceService.deleteWeComInboundTraceByIds(ids)); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/WeComInboundTrace.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/WeComInboundTrace.java new file mode 100644 index 0000000..501660c --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/WeComInboundTrace.java @@ -0,0 +1,134 @@ +package com.ruoyi.jarvis.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +import java.util.Date; + +/** + * 企微 inbound 消息追踪 wecom_inbound_trace + */ +public class WeComInboundTrace extends BaseEntity { + + private static final long serialVersionUID = 1L; + + private Long id; + + @Excel(name = "消息ID") + private String msgId; + + @Excel(name = "AgentID") + private String agentId; + + @Excel(name = "CorpId") + private String corpId; + + @Excel(name = "发送人UserID") + private String fromUserName; + + private String content; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "微信发送时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date wxMsgTime; + + private String replyContent; + + @Excel(name = "会话进行中", readConverterExp = "0=否,1=是") + private Integer sessionActive; + + @Excel(name = "会话场景") + private String sessionScene; + + @Excel(name = "会话步骤") + private String sessionStep; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMsgId() { + return msgId; + } + + public void setMsgId(String msgId) { + this.msgId = msgId; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getCorpId() { + return corpId; + } + + public void setCorpId(String corpId) { + this.corpId = corpId; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Date getWxMsgTime() { + return wxMsgTime; + } + + public void setWxMsgTime(Date wxMsgTime) { + this.wxMsgTime = wxMsgTime; + } + + public String getReplyContent() { + return replyContent; + } + + public void setReplyContent(String replyContent) { + this.replyContent = replyContent; + } + + public Integer getSessionActive() { + return sessionActive; + } + + public void setSessionActive(Integer sessionActive) { + this.sessionActive = sessionActive; + } + + public String getSessionScene() { + return sessionScene; + } + + public void setSessionScene(String sessionScene) { + this.sessionScene = sessionScene; + } + + public String getSessionStep() { + return sessionStep; + } + + public void setSessionStep(String sessionStep) { + this.sessionStep = sessionStep; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java index 2cbf586..f1e91d0 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/domain/dto/WeComInboundRequest.java @@ -15,6 +15,8 @@ public class WeComInboundRequest { private String toUserName; private String agentId; private String msgId; + /** 企微 XML CreateTime,秒级 Unix 时间戳(wxSend 传入) */ + private Long wxCreateTime; public String getFromUserName() { return fromUserName; @@ -55,4 +57,12 @@ public class WeComInboundRequest { public void setMsgId(String msgId) { this.msgId = msgId; } + + public Long getWxCreateTime() { + return wxCreateTime; + } + + public void setWxCreateTime(Long wxCreateTime) { + this.wxCreateTime = wxCreateTime; + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComInboundTraceMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComInboundTraceMapper.java new file mode 100644 index 0000000..de315d3 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/WeComInboundTraceMapper.java @@ -0,0 +1,16 @@ +package com.ruoyi.jarvis.mapper; + +import com.ruoyi.jarvis.domain.WeComInboundTrace; + +import java.util.List; + +public interface WeComInboundTraceMapper { + + int insertWeComInboundTrace(WeComInboundTrace trace); + + WeComInboundTrace selectWeComInboundTraceById(Long id); + + List selectWeComInboundTraceList(WeComInboundTrace query); + + int deleteWeComInboundTraceByIds(Long[] ids); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComInboundTraceService.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComInboundTraceService.java new file mode 100644 index 0000000..3351b85 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/IWeComInboundTraceService.java @@ -0,0 +1,17 @@ +package com.ruoyi.jarvis.service; + +import com.ruoyi.jarvis.domain.WeComInboundTrace; +import com.ruoyi.jarvis.domain.dto.WeComInboundRequest; + +import java.util.List; + +public interface IWeComInboundTraceService { + + void recordInbound(WeComInboundRequest request, String reply); + + WeComInboundTrace selectWeComInboundTraceById(Long id); + + List selectWeComInboundTraceList(WeComInboundTrace query); + + int deleteWeComInboundTraceByIds(Long[] ids); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundTraceServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundTraceServiceImpl.java new file mode 100644 index 0000000..2c0253b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WeComInboundTraceServiceImpl.java @@ -0,0 +1,73 @@ +package com.ruoyi.jarvis.service.impl; + +import com.ruoyi.jarvis.domain.WeComInboundTrace; +import com.ruoyi.jarvis.domain.dto.WeComChatSession; +import com.ruoyi.jarvis.domain.dto.WeComInboundRequest; +import com.ruoyi.jarvis.mapper.WeComInboundTraceMapper; +import com.ruoyi.jarvis.service.IWeComChatSessionService; +import com.ruoyi.jarvis.service.IWeComInboundTraceService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Service +public class WeComInboundTraceServiceImpl implements IWeComInboundTraceService { + + private static final Logger log = LoggerFactory.getLogger(WeComInboundTraceServiceImpl.class); + + @Resource + private WeComInboundTraceMapper weComInboundTraceMapper; + @Resource + private IWeComChatSessionService weComChatSessionService; + + @Override + public void recordInbound(WeComInboundRequest request, String reply) { + if (request == null || !StringUtils.hasText(request.getFromUserName())) { + return; + } + try { + String from = request.getFromUserName().trim(); + WeComInboundTrace trace = new WeComInboundTrace(); + trace.setMsgId(request.getMsgId()); + trace.setAgentId(request.getAgentId()); + trace.setCorpId(request.getToUserName()); + trace.setFromUserName(from); + trace.setContent(request.getContent()); + Long wxSec = request.getWxCreateTime(); + if (wxSec != null && wxSec > 0) { + trace.setWxMsgTime(new Date(wxSec * 1000L)); + } + trace.setReplyContent(reply != null ? reply : ""); + + WeComChatSession session = weComChatSessionService.get(from); + trace.setSessionActive(session != null ? 1 : 0); + if (session != null) { + trace.setSessionScene(session.getScene()); + trace.setSessionStep(session.getStep()); + } + weComInboundTraceMapper.insertWeComInboundTrace(trace); + } catch (Exception e) { + log.warn("企微消息追踪落库失败: {}", e.toString()); + } + } + + @Override + public WeComInboundTrace selectWeComInboundTraceById(Long id) { + return weComInboundTraceMapper.selectWeComInboundTraceById(id); + } + + @Override + public List selectWeComInboundTraceList(WeComInboundTrace query) { + return weComInboundTraceMapper.selectWeComInboundTraceList(query); + } + + @Override + public int deleteWeComInboundTraceByIds(Long[] ids) { + return weComInboundTraceMapper.deleteWeComInboundTraceByIds(ids); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/WeComInboundTraceMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/WeComInboundTraceMapper.xml new file mode 100644 index 0000000..6b8b41c --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/jarvis/WeComInboundTraceMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + select id, msg_id, agent_id, corp_id, from_user_name, content, wx_msg_time, + reply_content, session_active, session_scene, session_step, create_time + from wecom_inbound_trace + + + + insert into wecom_inbound_trace ( + msg_id, agent_id, corp_id, from_user_name, content, wx_msg_time, + reply_content, session_active, session_scene, session_step + ) values ( + #{msgId}, #{agentId}, #{corpId}, #{fromUserName}, #{content}, #{wxMsgTime}, + #{replyContent}, #{sessionActive}, #{sessionScene}, #{sessionStep} + ) + + + + + + + + delete from wecom_inbound_trace where id in + + #{id} + + + diff --git a/sql/wecom_inbound_trace.sql b/sql/wecom_inbound_trace.sql new file mode 100644 index 0000000..0674304 --- /dev/null +++ b/sql/wecom_inbound_trace.sql @@ -0,0 +1,33 @@ +-- 企业微信桥接消息追踪(Jarvis 落库,供后台查看) +DROP TABLE IF EXISTS `wecom_inbound_trace`; +CREATE TABLE `wecom_inbound_trace` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', + `msg_id` varchar(64) DEFAULT NULL COMMENT '企微消息 MsgId', + `agent_id` varchar(32) DEFAULT NULL COMMENT '应用 AgentID', + `corp_id` varchar(64) DEFAULT NULL COMMENT '企业 CorpId(XML ToUserName)', + `from_user_name` varchar(128) NOT NULL COMMENT '发送人成员 UserID(FromUserName)', + `content` mediumtext COMMENT '用户发送内容', + `wx_msg_time` datetime DEFAULT NULL COMMENT '微信侧 CreateTime(秒级时间戳转换)', + `reply_content` mediumtext COMMENT 'Jarvis 返回给 wxSend 的回复文本', + `session_active` tinyint(4) NOT NULL DEFAULT 0 COMMENT '处理完成后是否存在多轮会话:0否 1是', + `session_scene` varchar(64) DEFAULT NULL COMMENT '会话场景(如 JD_LOGISTICS_SHARE)', + `session_step` varchar(64) DEFAULT NULL COMMENT '会话步骤(如 WAIT_REMARK)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '服务端接收处理时间', + PRIMARY KEY (`id`), + KEY `idx_from_user` (`from_user_name`), + KEY `idx_msg_id` (`msg_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企微 inbound 消息追踪'; + +-- 菜单:挂在「系统监控」下,与日志文件类似;执行后请给需要角色分配该菜单权限 +INSERT INTO sys_menu VALUES ( + 2090, '企微消息跟踪', 2, 8, 'wecomInboundTrace', 'jarvis/wecomInboundTrace/index', '', '', 1, 0, 'C', '0', '0', + 'jarvis:wecom:inboundTrace:list', 'wechat', 'admin', sysdate(), '', NULL, '企微回调经 Jarvis 处理记录' +); +INSERT INTO sys_menu VALUES ( + 2092, '删除', 2090, 1, '', '', '', '', 1, 0, 'F', '0', '0', + 'jarvis:wecom:inboundTrace:remove', '#', 'admin', sysdate(), '', NULL, '' +); + +-- 若需超级管理员角色默认拥有(role_id 按实际调整,常见管理员为 1) +-- INSERT INTO sys_role_menu (role_id, menu_id) VALUES (1, 2090), (1, 2092);